Lower host class interop on the Chez back end. The analyzer now turns a non-var qualified ref `Class/member` into a :host-static node and a `(Class. ...)` / `(new Class ...)` form into a :host-new node (ir.clj gains both, with walker support). The Janet back end punts both to the interpreter, so its behavior is unchanged (verified: dot-form, `..` threading, shadowed `new`, and all interop still resolve via fallback). The Chez emit lowers a value ref to host-static-ref, a call head to host-static-call, and a constructor to host-new. host/chez/host-static.ss is the runtime registry these resolve against — the Chez port of the seed's class-statics / class-ctors / tagged-methods (java_base.janet + host_io.janet), restricted to the java.lang/util/net/io surface portable cljc code calls: Math, System (getenv/getProperty/exit/currentTimeMillis), Long, Integer, Boolean, Character, String, Thread, Class, Pattern (compile/quote/MULTILINE), URLEncoder/Decoder, Base64, the Number method surface (byteValue/intValue/...), plus the StringBuilder, StringWriter, StringReader, PushbackReader, HashMap, StringTokenizer, BigInteger, String, MapEntry, and exception constructors. Constructed objects are jhost records dispatched through record-method-dispatch. Also: emit now evaluates collection-literal elements left-to-right (emit-ordered) — Chez evaluates call args right-to-left, which had been swapping side-effecting elements in [(read r) (read r)] and map literals. This un-allowlisted the 6 eval-order corpus cases (the read-line trio + the three map-construction cases). Removed `.write` from the jolt-host-call fast-path so a StringWriter routes through dispatch. java.time formatting, edn/read-over-readers, and slurp/with-open over readers are deferred to a follow-up. Corpus parity 2078 -> 2134 (floor raised), 0 new divergences; the print-method builtin-override case is allowlisted (same multimethod gap, newly reachable now that StringWriter constructs). emit-test 326/326, _javastatic 51/51, conformance 355x3, full jpm test green.
476 lines
27 KiB
Scheme
476 lines
27 KiB
Scheme
;; host-static.ss (jolt-avt6) — host class statics + constructors on Chez.
|
|
;;
|
|
;; The analyzer lowers `Class/member` to a :host-static node and `(Class. ...)` /
|
|
;; `(new Class ...)` to a :host-new node (jolt-core/jolt/analyzer.clj); the Chez
|
|
;; emit (emit.janet) lowers a value ref to (host-static-ref "Class" "member"), a
|
|
;; call head to (host-static-call "Class" "member" args...), and a constructor to
|
|
;; (host-new "Class" args...). This file is the runtime registry those three
|
|
;; resolve against — the Chez port of the seed's class-statics / class-ctors /
|
|
;; tagged-methods registries (src/jolt/interop/java_base.janet + host_io.janet),
|
|
;; restricted to the java.lang/util/net/io surface portable cljc code calls.
|
|
;; (java.time formatting is a separate increment.)
|
|
;;
|
|
;; Constructed host objects are `jhost` records (a tag + mutable state); their
|
|
;; (.method ...) calls reach record-method-dispatch (records.ss), extended below
|
|
;; with a jhost arm that dispatches through host-tagged-methods.
|
|
;;
|
|
;; Loaded from rt.ss LAST (after natives-str.ss / records.ss): it extends
|
|
;; record-method-dispatch and reuses jolt-str-render-one / jolt-re-pattern.
|
|
|
|
;; ---- registries -------------------------------------------------------------
|
|
(define class-statics-tbl (make-hashtable string-hash string=?)) ; "Class" -> (member-ht)
|
|
(define class-ctors-tbl (make-hashtable string-hash string=?)) ; "Class" -> ctor proc
|
|
(define host-methods-tbl (make-hashtable string-hash string=?)) ; tag -> (method-ht)
|
|
|
|
;; A class token may arrive fully qualified (java.io.StringReader) or short
|
|
;; (StringReader). Register both; resolve by exact then by last dotted segment.
|
|
(define (short-class-name s)
|
|
(let loop ((i (- (string-length s) 1)))
|
|
(cond ((< i 0) s)
|
|
((char=? (string-ref s i) #\.) (substring s (+ i 1) (string-length s)))
|
|
(else (loop (- i 1))))))
|
|
|
|
(define (register-class-statics! name members) ; members: list of (str . val/proc)
|
|
(let ((h (or (hashtable-ref class-statics-tbl name #f)
|
|
(let ((h (make-hashtable string-hash string=?)))
|
|
(hashtable-set! class-statics-tbl name h) h))))
|
|
(for-each (lambda (p) (hashtable-set! h (car p) (cdr p))) members)))
|
|
|
|
(define (register-class-ctor! name proc) (hashtable-set! class-ctors-tbl name proc))
|
|
|
|
(define (register-host-methods! tag members)
|
|
(let ((h (or (hashtable-ref host-methods-tbl tag #f)
|
|
(let ((h (make-hashtable string-hash string=?)))
|
|
(hashtable-set! host-methods-tbl tag h) h))))
|
|
(for-each (lambda (p) (hashtable-set! h (car p) (cdr p))) members)))
|
|
|
|
(define (lookup-class h-tbl name)
|
|
(or (hashtable-ref h-tbl name #f)
|
|
(hashtable-ref h-tbl (short-class-name name) #f)))
|
|
|
|
;; ---- host object ------------------------------------------------------------
|
|
(define-record-type jhost (fields tag (mutable state)) (nongenerative chez-jhost-v1))
|
|
|
|
;; record-method-dispatch (records.ss) gets a jhost arm: dispatch (.method obj a*)
|
|
;; through the tag's method table.
|
|
(define %hs-record-method-dispatch record-method-dispatch)
|
|
(set! record-method-dispatch
|
|
(lambda (obj method-name rest-args)
|
|
(cond
|
|
((jhost? obj)
|
|
(let ((mh (hashtable-ref host-methods-tbl (jhost-tag obj) #f)))
|
|
(let ((f (and mh (hashtable-ref mh method-name #f))))
|
|
(if f
|
|
(apply f obj (if (jolt-nil? rest-args) '() (seq->list rest-args)))
|
|
(error #f (string-append "No method " method-name " on host " (jhost-tag obj)))))))
|
|
((number? obj) (number-method method-name obj))
|
|
(else (%hs-record-method-dispatch obj method-name rest-args)))))
|
|
|
|
;; java.lang.Number method surface (the boxed-number methods cljc code calls). The
|
|
;; integer projections wrap modulo their width (ring-codec relies on byteValue
|
|
;; overflow: (.byteValue 255) => -1); the float projections are identity flonums.
|
|
(define (number-method method n)
|
|
(cond
|
|
((string=? method "byteValue") (let ((b (modulo (jnum->exact n) 256))) (->num (if (>= b 128) (- b 256) b))))
|
|
((string=? method "shortValue") (let ((b (modulo (jnum->exact n) 65536))) (->num (if (>= b 32768) (- b 65536) b))))
|
|
((string=? method "intValue") (->num (jnum->exact n)))
|
|
((string=? method "longValue") (->num (jnum->exact n)))
|
|
((string=? method "doubleValue") (->num n))
|
|
((string=? method "floatValue") (->num n))
|
|
((string=? method "toString") (jolt-num->string n))
|
|
((string=? method "hashCode") (->num (jnum->exact n)))
|
|
(else (error #f (string-append "No method " method " for number")))))
|
|
|
|
;; ---- emit entry points ------------------------------------------------------
|
|
(define (host-static-ref class member)
|
|
(let ((h (lookup-class class-statics-tbl class)))
|
|
(if h
|
|
(let ((v (hashtable-ref h member #f)))
|
|
(if v v (error #f (string-append "No static " class "/" member))))
|
|
(error #f (string-append "Unknown class " class)))))
|
|
|
|
(define (host-static-call class member . args)
|
|
(apply (host-static-ref class member) args))
|
|
|
|
(define (host-new class . args)
|
|
(let ((ctor (lookup-class class-ctors-tbl class)))
|
|
(if ctor (apply ctor args)
|
|
(error #f (string-append "No constructor for class " class)))))
|
|
|
|
;; ---- coercion helpers -------------------------------------------------------
|
|
(define (->num x) (exact->inexact x))
|
|
(define (jnum->exact n) (exact (truncate n)))
|
|
;; parse an integer string in radix; #f on failure (matches the seed's scan-number)
|
|
(define (parse-int-str s radix)
|
|
(let ((n (string->number (str-trim (if (string? s) s (jolt-str-render-one s))) radix)))
|
|
(and n (integer? n) (->num n))))
|
|
(define (parse-int-or-throw s radix what)
|
|
(or (parse-int-str s radix)
|
|
(error #f (string-append "NumberFormatException: For input string: \""
|
|
(if (string? s) s (jolt-str-render-one s)) "\""))))
|
|
(define (char-code c) (if (char? c) (char->integer c) (jnum->exact c)))
|
|
|
|
;; ---- java.lang statics ------------------------------------------------------
|
|
(register-class-statics! "Math"
|
|
(list (cons "sqrt" (lambda (x) (->num (sqrt x))))
|
|
(cons "pow" (lambda (a b) (->num (expt a b))))
|
|
(cons "floor" (lambda (x) (->num (floor x))))
|
|
(cons "ceil" (lambda (x) (->num (ceiling x))))
|
|
(cons "round" (lambda (x) (->num (round x))))
|
|
(cons "abs" (lambda (x) (->num (abs x))))
|
|
(cons "sin" (lambda (x) (->num (sin x)))) (cons "cos" (lambda (x) (->num (cos x))))
|
|
(cons "tan" (lambda (x) (->num (tan x)))) (cons "asin" (lambda (x) (->num (asin x))))
|
|
(cons "acos" (lambda (x) (->num (acos x)))) (cons "atan" (lambda (x) (->num (atan x))))
|
|
(cons "log" (lambda (x) (->num (log x)))) (cons "log10" (lambda (x) (->num (/ (log x) (log 10)))))
|
|
(cons "exp" (lambda (x) (->num (exp x))))
|
|
(cons "max" (lambda (a b) (->num (if (> a b) a b)))) (cons "min" (lambda (a b) (->num (if (< a b) a b))))
|
|
(cons "signum" (lambda (x) (cond ((< x 0) -1.0) ((> x 0) 1.0) (else 0.0))))
|
|
(cons "PI" (->num (* 4 (atan 1)))) (cons "E" (->num (exp 1)))
|
|
(cons "random" (lambda args (->num (random 1.0))))))
|
|
|
|
;; Thread: no real threading here (futures/timing are jolt-byjr). sleep is a no-op.
|
|
(register-class-statics! "Thread"
|
|
(list (cons "sleep" (lambda (ms . _) jolt-nil))
|
|
(cons "yield" (lambda () jolt-nil))
|
|
(cons "interrupted" (lambda () #f))
|
|
(cons "currentThread" (lambda () (make-jhost "thread" '())))))
|
|
(register-host-methods! "thread"
|
|
(list (cons "getContextClassLoader" (lambda (self) (make-jhost "classloader" '())))))
|
|
|
|
(define (now-millis)
|
|
(let ((t (current-time 'time-utc)))
|
|
(+ (* 1000 (time-second t)) (quotient (time-nanosecond t) 1000000))))
|
|
(register-class-statics! "System"
|
|
(list (cons "currentTimeMillis" (lambda () (->num (now-millis))))
|
|
(cons "nanoTime" (lambda () (->num (* 1000000 (now-millis)))))
|
|
(cons "exit" (lambda args (exit (if (null? args) 0 (jnum->exact (car args))))))
|
|
;; wrapped in lambdas: the helpers are defined below, resolved at call time.
|
|
(cons "getProperty" (lambda (k . d) (apply sys-get-property k d)))
|
|
(cons "getProperties" (lambda () (sys-properties-map)))
|
|
(cons "getenv" (lambda k (apply sys-getenv k)))))
|
|
|
|
(register-class-statics! "Long"
|
|
(list (cons "MAX_VALUE" (->num 9223372036854775807))
|
|
(cons "MIN_VALUE" (->num -9223372036854775808))
|
|
(cons "parseLong" (lambda (s . r) (parse-int-or-throw s (if (null? r) 10 (jnum->exact (car r))) "parseLong")))
|
|
(cons "valueOf" (lambda (s . r) (parse-int-or-throw s (if (null? r) 10 (jnum->exact (car r))) "valueOf")))))
|
|
|
|
(register-class-statics! "Integer"
|
|
(list (cons "MAX_VALUE" (->num 2147483647)) (cons "MIN_VALUE" (->num -2147483648))
|
|
(cons "valueOf" (lambda (x . r)
|
|
(if (number? x) (->num x)
|
|
(parse-int-or-throw x (if (null? r) 10 (jnum->exact (car r))) "valueOf"))))
|
|
(cons "parseInt" (lambda (x . r) (parse-int-or-throw x (if (null? r) 10 (jnum->exact (car r))) "parseInt")))))
|
|
|
|
(register-class-statics! "Boolean"
|
|
(list (cons "parseBoolean" (lambda (s) (string=? "true" (ascii-string-down (if (string? s) s (jolt-str-render-one s))))))
|
|
(cons "TRUE" #t) (cons "FALSE" #f)))
|
|
|
|
;; Character: ASCII predicates (the engine is byte/ASCII oriented).
|
|
(register-class-statics! "Character"
|
|
(list (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 "isDigit" (lambda (c) (let ((n (char-code c))) (and (>= n 48) (<= n 57)))))
|
|
(cons "isWhitespace" (lambda (c) (char<=? (integer->char (char-code c)) #\space)))))
|
|
|
|
;; String/valueOf(Object): "null" for nil, else jolt's str semantics.
|
|
(register-class-statics! "String"
|
|
(list (cons "valueOf" (lambda (x . _) (if (jolt-nil? x) "null" (jolt-str-render-one x))))))
|
|
|
|
(register-class-statics! "Class"
|
|
(list (cons "forName" (lambda (nm) (make-jhost "class" (list (cons 'name nm)))))))
|
|
|
|
;; ---- System helpers (defined before use above via top-level order) ----------
|
|
(define (sys-get-property k . dflt)
|
|
(cond ((string=? k "os.name") "Mac OS X")
|
|
((string=? k "line.separator") "\n")
|
|
((string=? k "file.separator") "/")
|
|
((string=? k "path.separator") ":")
|
|
((string=? k "user.dir") (or (getenv "PWD") "."))
|
|
((string=? k "user.home") (or (getenv "HOME") ""))
|
|
((string=? k "java.io.tmpdir") (or (getenv "TMPDIR") "/tmp"))
|
|
((pair? dflt) (car dflt))
|
|
(else jolt-nil)))
|
|
(define (sys-properties-map)
|
|
(jolt-hash-map "os.name" "Mac OS X" "line.separator" "\n" "file.separator" "/"
|
|
"user.dir" (or (getenv "PWD") ".") "user.home" (or (getenv "HOME") "")
|
|
"java.io.tmpdir" (or (getenv "TMPDIR") "/tmp")))
|
|
|
|
;; full environment as an alist of (name . value), via spawning `env`.
|
|
(define (all-env-pairs)
|
|
(call-with-values
|
|
(lambda () (open-process-ports "env" (buffer-mode block) (native-transcoder)))
|
|
(lambda (stdin stdout stderr pid)
|
|
(let loop ((acc '()))
|
|
(let ((l (get-line stdout)))
|
|
(if (eof-object? l) (reverse acc)
|
|
(let ((eq (let scan ((i 0)) (cond ((= i (string-length l)) #f)
|
|
((char=? (string-ref l i) #\=) i)
|
|
(else (scan (+ i 1)))))))
|
|
(loop (if eq (cons (cons (substring l 0 eq) (substring l (+ eq 1) (string-length l))) acc) acc)))))))))
|
|
;; JOLT_BAKE_ENV_ALLOWLIST (jolt-s3j): when set, only the listed comma-separated
|
|
;; names are served; unset (the normal case) reads are live and unfiltered.
|
|
(define (env-allowlist)
|
|
(let ((a (getenv "JOLT_BAKE_ENV_ALLOWLIST")))
|
|
(and a (map str-trim (str-literal-split a ",")))))
|
|
(define (sys-getenv . k)
|
|
(let ((allow (env-allowlist)))
|
|
(if (null? k)
|
|
(apply jolt-hash-map
|
|
(let loop ((ps (all-env-pairs)) (acc '()))
|
|
(cond ((null? ps) (reverse acc))
|
|
((and allow (not (member (caar ps) allow))) (loop (cdr ps) acc))
|
|
(else (loop (cdr ps) (cons (cdar ps) (cons (caar ps) acc)))))))
|
|
(let ((name (car k)))
|
|
(if (and allow (not (member name allow))) jolt-nil
|
|
(let ((v (getenv name))) (if v v jolt-nil)))))))
|
|
|
|
;; ---- StringBuilder ----------------------------------------------------------
|
|
;; state: a box (1-vector) holding the accumulated string.
|
|
(define (sb-str self) (vector-ref (jhost-state self) 0))
|
|
(define (sb-set! self s) (vector-set! (jhost-state self) 0 s))
|
|
(define (render-piece x)
|
|
(cond ((jolt-nil? x) "null") ((char? x) (string x)) ((string? x) x)
|
|
(else (jolt-str-render-one x))))
|
|
(register-class-ctor! "StringBuilder"
|
|
(lambda args (make-jhost "string-builder"
|
|
;; a numeric first arg is a CAPACITY hint, not content.
|
|
(vector (if (and (pair? args) (not (number? (car args)))) (render-piece (car args)) "")))))
|
|
(register-host-methods! "string-builder"
|
|
(list (cons "append" (lambda (self x) (sb-set! self (string-append (sb-str self) (render-piece x))) self))
|
|
(cons "toString" (lambda (self) (sb-str self)))
|
|
(cons "length" (lambda (self) (->num (string-length (sb-str self)))))
|
|
(cons "charAt" (lambda (self i) (string-ref (sb-str self) (jnum->exact i))))
|
|
(cons "setLength" (lambda (self n)
|
|
(let ((cur (sb-str self)) (n (jnum->exact n)))
|
|
(sb-set! self (if (< n (string-length cur))
|
|
(substring cur 0 n)
|
|
(string-append cur (make-string (- n (string-length cur)) #\nul)))))
|
|
jolt-nil))))
|
|
|
|
;; ---- StringWriter -----------------------------------------------------------
|
|
;; Writer.write(int) writes the CHAR for that code; append(char) appends the char.
|
|
(define (writer-piece x) (if (number? x) (string (integer->char (jnum->exact x))) (render-piece x)))
|
|
(register-class-ctor! "StringWriter" (lambda args (make-jhost "writer" (vector ""))))
|
|
(register-host-methods! "writer"
|
|
(list (cons "write" (lambda (self x) (sb-set! self (string-append (sb-str self) (writer-piece x))) jolt-nil))
|
|
(cons "append" (lambda (self x) (sb-set! self (string-append (sb-str self) (render-piece x))) self))
|
|
(cons "flush" (lambda (self) jolt-nil))
|
|
(cons "close" (lambda (self) jolt-nil))
|
|
(cons "toString" (lambda (self) (sb-str self)))))
|
|
|
|
;; ---- StringReader -----------------------------------------------------------
|
|
;; state: a vector #(string pos marked).
|
|
(register-class-ctor! "StringReader"
|
|
(lambda (src . _) (make-jhost "string-reader" (vector (if (string? src) src (jolt-str-render-one src)) 0 0))))
|
|
(define (sr-s self) (vector-ref (jhost-state self) 0))
|
|
(define (sr-pos self) (vector-ref (jhost-state self) 1))
|
|
(define (sr-pos! self p) (vector-set! (jhost-state self) 1 p))
|
|
(register-host-methods! "string-reader"
|
|
(list (cons "read" (lambda (self)
|
|
(let ((s (sr-s self)) (p (sr-pos self)))
|
|
(if (>= p (string-length s)) -1.0
|
|
(begin (sr-pos! self (+ p 1)) (->num (char->integer (string-ref s p))))))))
|
|
(cons "mark" (lambda (self . _) (vector-set! (jhost-state self) 2 (sr-pos self)) jolt-nil))
|
|
(cons "reset" (lambda (self) (sr-pos! self (vector-ref (jhost-state self) 2)) jolt-nil))
|
|
(cons "skip" (lambda (self n) (let ((n (jnum->exact n)))
|
|
(sr-pos! self (min (string-length (sr-s self)) (+ (sr-pos self) n))) (->num n))))
|
|
(cons "close" (lambda (self) jolt-nil))))
|
|
|
|
;; ---- PushbackReader ---------------------------------------------------------
|
|
;; state: a vector #(wrapped-reader pushed-list)
|
|
(register-class-ctor! "PushbackReader"
|
|
(lambda (rdr . _) (make-jhost "pushback-reader" (vector rdr '()))))
|
|
(define (read-unit r) ; read one code unit (flonum) from any reader, -1 at EOF
|
|
(record-method-dispatch r "read" jolt-nil))
|
|
(register-host-methods! "pushback-reader"
|
|
(list (cons "read" (lambda (self)
|
|
(let ((pushed (vector-ref (jhost-state self) 1)))
|
|
(if (pair? pushed)
|
|
(begin (vector-set! (jhost-state self) 1 (cdr pushed)) (car pushed))
|
|
(read-unit (vector-ref (jhost-state self) 0))))))
|
|
(cons "unread" (lambda (self ch)
|
|
(vector-set! (jhost-state self) 1
|
|
(cons (if (char? ch) (->num (char->integer ch)) ch) (vector-ref (jhost-state self) 1)))
|
|
jolt-nil))
|
|
(cons "close" (lambda (self) jolt-nil))))
|
|
|
|
;; ---- HashMap ----------------------------------------------------------------
|
|
;; state: a box holding an alist of (k . v), jolt= keyed.
|
|
(define (hm-alist self) (vector-ref (jhost-state self) 0))
|
|
(define (hm-set! self al) (vector-set! (jhost-state self) 0 al))
|
|
(define (hm-assoc al k v)
|
|
(let loop ((ps al) (acc '()) (hit #f))
|
|
(cond ((null? ps) (reverse (if hit acc (cons (cons k v) acc))))
|
|
((jolt=2 (caar ps) k) (loop (cdr ps) (cons (cons k v) acc) #t))
|
|
(else (loop (cdr ps) (cons (car ps) acc) hit)))))
|
|
(define (hm-get al k) (let loop ((ps al)) (cond ((null? ps) jolt-nil) ((jolt=2 (caar ps) k) (cdar ps)) (else (loop (cdr ps))))))
|
|
(define (coll->pairs m)
|
|
(if (jolt-nil? m) '()
|
|
(let loop ((s (jolt-seq m)) (acc '()))
|
|
(if (jolt-nil? s) (reverse acc)
|
|
(let ((e (seq-first s))) (loop (jolt-seq (seq-more s)) (cons (cons (jolt-nth e 0) (jolt-nth e 1)) acc)))))))
|
|
(register-class-ctor! "HashMap"
|
|
(lambda args
|
|
(let ((init (and (pair? args) (car args))))
|
|
(make-jhost "hashmap" (vector (if (and init (not (number? init))) (coll->pairs init) '()))))))
|
|
(register-host-methods! "hashmap"
|
|
(list (cons "get" (lambda (self k) (hm-get (hm-alist self) k)))
|
|
(cons "put" (lambda (self k v) (hm-set! self (hm-assoc (hm-alist self) k v)) v))
|
|
(cons "putAll" (lambda (self m) (for-each (lambda (p) (hm-set! self (hm-assoc (hm-alist self) (car p) (cdr p)))) (coll->pairs m)) jolt-nil))
|
|
(cons "containsKey" (lambda (self k) (not (jolt-nil? (hm-get (hm-alist self) k)))))
|
|
(cons "size" (lambda (self) (->num (length (hm-alist self)))))))
|
|
|
|
;; ---- StringTokenizer --------------------------------------------------------
|
|
;; state: a vector #(tokens-list pos)
|
|
(define (tokenize s delims)
|
|
(let ((dset (string->list delims)))
|
|
(let loop ((chars (string->list s)) (cur '()) (toks '()))
|
|
(cond ((null? chars) (reverse (if (null? cur) toks (cons (list->string (reverse cur)) toks))))
|
|
((memv (car chars) dset)
|
|
(loop (cdr chars) '() (if (null? cur) toks (cons (list->string (reverse cur)) toks))))
|
|
(else (loop (cdr chars) (cons (car chars) cur) toks))))))
|
|
(register-class-ctor! "StringTokenizer"
|
|
(lambda (s . delims) (make-jhost "string-tokenizer"
|
|
(vector (tokenize (if (string? s) s (jolt-str-render-one s))
|
|
(if (null? delims) " \t\n\r\f" (car delims))) 0))))
|
|
(register-host-methods! "string-tokenizer"
|
|
(list (cons "hasMoreTokens" (lambda (self) (< (vector-ref (jhost-state self) 1) (length (vector-ref (jhost-state self) 0)))))
|
|
(cons "countTokens" (lambda (self) (->num (- (length (vector-ref (jhost-state self) 0)) (vector-ref (jhost-state self) 1)))))
|
|
(cons "nextToken" (lambda (self)
|
|
(let ((toks (vector-ref (jhost-state self) 0)) (p (vector-ref (jhost-state self) 1)))
|
|
(if (< p (length toks))
|
|
(begin (vector-set! (jhost-state self) 1 (+ p 1)) (list-ref toks p))
|
|
(error #f "NoSuchElementException")))))))
|
|
|
|
;; ---- String / BigInteger / MapEntry constructors ----------------------------
|
|
;; (String. x) / (String. bytes): a bytevector decodes UTF-8; else stringify.
|
|
(register-class-ctor! "String"
|
|
(lambda (x . _) (cond ((bytevector? x) (utf8->string x)) ((string? x) x) (else (jolt-str-render-one x)))))
|
|
(register-class-ctor! "BigInteger"
|
|
(lambda (v) (parse-int-or-throw v 10 "BigInteger")))
|
|
(register-class-ctor! "MapEntry" (lambda (k v) (make-map-entry k v)))
|
|
;; JVM exception ctors: keep the message string (so getMessage / ex-message work).
|
|
(for-each (lambda (nm) (register-class-ctor! nm (lambda (msg . _) (if (string? msg) msg (jolt-str-render-one msg)))))
|
|
'("Exception" "RuntimeException" "IllegalArgumentException" "IllegalStateException"
|
|
"InterruptedException" "UnsupportedOperationException" "IOException"))
|
|
|
|
;; ---- URLEncoder / URLDecoder (www-form-urlencoded) --------------------------
|
|
(define (url-unreserved? b)
|
|
(or (and (>= b 48) (<= b 57)) (and (>= b 65) (<= b 90)) (and (>= b 97) (<= b 122))
|
|
(= b 46) (= b 42) (= b 95) (= b 45)))
|
|
(define hex-digits "0123456789ABCDEF")
|
|
(define (url-encode s . _)
|
|
(let ((bs (string->utf8 (if (string? s) s (jolt-str-render-one s)))) (out '()))
|
|
(let loop ((i 0))
|
|
(if (= i (bytevector-length bs)) (list->string (reverse out))
|
|
(let ((b (bytevector-u8-ref bs i)))
|
|
(cond ((url-unreserved? b) (set! out (cons (integer->char b) out)))
|
|
((= b 32) (set! out (cons #\+ out)))
|
|
(else (set! out (cons (string-ref hex-digits (bitwise-and b 15))
|
|
(cons (string-ref hex-digits (bitwise-arithmetic-shift-right b 4))
|
|
(cons #\% out))))))
|
|
(loop (+ i 1)))))))
|
|
(define (hexv c)
|
|
(cond ((and (char<=? #\0 c) (char<=? c #\9)) (- (char->integer c) 48))
|
|
((and (char<=? #\A c) (char<=? c #\F)) (- (char->integer c) 55))
|
|
((and (char<=? #\a c) (char<=? c #\f)) (- (char->integer c) 87))
|
|
(else (error #f "URLDecoder: malformed escape"))))
|
|
(define (url-decode s . _)
|
|
(let* ((str (if (string? s) s (jolt-str-render-one s))) (n (string-length str)) (out '()))
|
|
(let loop ((i 0))
|
|
(if (>= i n) (utf8->string (u8-list->bytevector (reverse out)))
|
|
(let ((c (string-ref str i)))
|
|
(cond ((char=? c #\+) (set! out (cons 32 out)) (loop (+ i 1)))
|
|
((char=? c #\%)
|
|
(set! out (cons (+ (* 16 (hexv (string-ref str (+ i 1)))) (hexv (string-ref str (+ i 2)))) out))
|
|
(loop (+ i 3)))
|
|
(else (set! out (cons (char->integer c) out)) (loop (+ i 1)))))))))
|
|
(define (u8-list->bytevector lst)
|
|
(let ((bv (make-bytevector (length lst))))
|
|
(let loop ((l lst) (i 0)) (if (null? l) bv (begin (bytevector-u8-set! bv i (car l)) (loop (cdr l) (+ i 1)))))))
|
|
(register-class-statics! "URLEncoder" (list (cons "encode" url-encode)))
|
|
(register-class-statics! "URLDecoder" (list (cons "decode" url-decode)))
|
|
(register-class-statics! "Charset" (list (cons "forName" (lambda (nm) (make-jhost "charset" (list (cons 'name nm)))))))
|
|
|
|
;; ---- Base64 (RFC 4648) ------------------------------------------------------
|
|
(define b64-alphabet "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
|
|
(define (->bytevector x) (cond ((bytevector? x) x) ((string? x) (string->utf8 x)) (else (string->utf8 (jolt-str-render-one x)))))
|
|
(define (b64-encode x)
|
|
(let* ((bs (->bytevector x)) (n (bytevector-length bs)) (out '()))
|
|
(let loop ((i 0))
|
|
(if (>= i n) (list->string (reverse out))
|
|
(let* ((b0 (bytevector-u8-ref bs i))
|
|
(b1 (if (< (+ i 1) n) (bytevector-u8-ref bs (+ i 1)) #f))
|
|
(b2 (if (< (+ i 2) n) (bytevector-u8-ref bs (+ i 2)) #f)))
|
|
(set! out (cons (string-ref b64-alphabet (bitwise-arithmetic-shift-right b0 2)) out))
|
|
(set! out (cons (string-ref b64-alphabet (bitwise-ior (bitwise-arithmetic-shift-left (bitwise-and b0 3) 4)
|
|
(bitwise-arithmetic-shift-right (or b1 0) 4))) out))
|
|
(set! out (cons (if b1 (string-ref b64-alphabet (bitwise-ior (bitwise-arithmetic-shift-left (bitwise-and b1 15) 2)
|
|
(bitwise-arithmetic-shift-right (or b2 0) 6))) #\=) out))
|
|
(set! out (cons (if b2 (string-ref b64-alphabet (bitwise-and b2 63)) #\=) out))
|
|
(loop (+ i 3)))))))
|
|
(define (b64-char-val c)
|
|
(let loop ((i 0)) (cond ((= i 64) (error #f "Base64: illegal character")) ((char=? (string-ref b64-alphabet i) c) i) (else (loop (+ i 1))))))
|
|
(define (b64-decode x)
|
|
(let* ((str (let ((s (if (string? x) x (utf8->string (->bytevector x)))))
|
|
(list->string (filter (lambda (c) (not (char=? c #\=))) (string->list s)))))
|
|
(out '()) (acc 0) (bits 0))
|
|
(for-each (lambda (c)
|
|
(set! acc (bitwise-ior (bitwise-arithmetic-shift-left acc 6) (b64-char-val c)))
|
|
(set! bits (+ bits 6))
|
|
(when (>= bits 8)
|
|
(set! bits (- bits 8))
|
|
(set! out (cons (bitwise-and (bitwise-arithmetic-shift-right acc bits) 255) out))))
|
|
(string->list str))
|
|
(u8-list->bytevector (reverse out))))
|
|
(register-host-methods! "b64-encoder"
|
|
(list (cons "encode" (lambda (self bs) (string->utf8 (b64-encode bs))))
|
|
(cons "encodeToString" (lambda (self bs) (b64-encode bs)))))
|
|
(register-host-methods! "b64-decoder"
|
|
(list (cons "decode" (lambda (self s) (b64-decode s)))))
|
|
(register-class-statics! "Base64"
|
|
(list (cons "getEncoder" (lambda () (make-jhost "b64-encoder" '())))
|
|
(cons "getDecoder" (lambda () (make-jhost "b64-decoder" '())))))
|
|
|
|
;; ---- java.util.regex.Pattern ------------------------------------------------
|
|
;; Pattern/compile returns a jolt-regex value (regex-t), so str/replace, re-find,
|
|
;; .split etc. accept it transparently.
|
|
(define pattern-multiline 8.0)
|
|
(define (pattern-quote s)
|
|
(let ((meta "\\.[]{}()*+-?^$|&") (s (if (string? s) s (jolt-str-render-one s))) (out '()))
|
|
(let loop ((i 0))
|
|
(if (= i (string-length s)) (list->string (reverse out))
|
|
(let ((c (string-ref s i)))
|
|
(when (memv c (string->list meta)) (set! out (cons #\\ out)))
|
|
(set! out (cons c out))
|
|
(loop (+ i 1)))))))
|
|
(register-class-statics! "Pattern"
|
|
(list (cons "compile" (lambda (s . flags)
|
|
(if (and (pair? flags) (= (bitwise-and (jnum->exact (car flags)) 8) 8))
|
|
(jolt-regex (string-append "(?m)" s))
|
|
(jolt-regex s))))
|
|
(cons "quote" (lambda (s) (pattern-quote s)))
|
|
(cons "MULTILINE" pattern-multiline)))
|
|
;; record-method-dispatch already routes string? -> jolt-string-method. Add a
|
|
;; regex-t arm (Pattern .split / .matcher-less surface used by corpus) by wrapping
|
|
;; once more — a regex-t isn't a jhost.
|
|
(define %hs-rmd2 record-method-dispatch)
|
|
(set! record-method-dispatch
|
|
(lambda (obj method-name rest-args)
|
|
(if (regex-t? obj)
|
|
(let ((rest (if (jolt-nil? rest-args) '() (seq->list rest-args))))
|
|
(cond ((string=? method-name "split")
|
|
;; .split returns a String[] — a seq, like the seed's array (prints
|
|
;; (a b c), not a vector). re-split with no limit; drop trailing
|
|
;; empties (JVM default).
|
|
(let ((parts (re-split (regex-t-irx obj) (car rest) #f)))
|
|
(list->cseq (str-split-drop-trailing parts))))
|
|
((string=? method-name "pattern") (regex-t-source obj))
|
|
(else (error #f (string-append "No method " method-name " on Pattern")))))
|
|
(%hs-rmd2 obj method-name rest-args))))
|
|
|
|
;; ---- def-var! the registry entry points so emit can also reach them ---------
|
|
(def-var! "clojure.core" "host-static-ref" host-static-ref)
|
|
(def-var! "clojure.core" "host-static-call" (lambda (c m . a) (apply host-static-call c m a)))
|
|
(def-var! "clojure.core" "host-new" (lambda (c . a) (apply host-new c a)))
|