Host completeness for the ring stack (ring-app)

Gaps surfaced loading ring-core / hiccup / config / tools.logging on Chez:

- clojure.java.io/resource — resolve a named resource against the loader's
  source roots (no classpath), returning a slurp-able File.
- (Object.) constructor — a fresh distinct value (lock / unique sentinel).
- *out* / *err* as dynamic vars over a port-writer, so (binding [*out* *err*] …)
  and #'*out* compile and run (tools.logging, selmer).
- :refer :all now registers a refer-all relation, so an unqualified var from a
  (require '[ns :refer :all]) resolves at compile time — including #'var.
- java.lang.Character interop ((.toString \+) etc.).
- StringReader accepts a char[] ((StringReader. (char-array s))), not just a
  String.
- the \p{L} translation stops just below the UTF-16 surrogate gap (\x{D7FF})
  instead of \x{10FFFF} — a range across the surrogates made irregex's char-set
  construction call integer->char on a surrogate and crash.
This commit is contained in:
Yogthos 2026-06-22 03:20:31 -04:00
parent 5e916433b8
commit 10e3a00777
5 changed files with 59 additions and 8 deletions

View file

@ -267,6 +267,10 @@
(define (render-piece x)
(cond ((jolt-nil? x) "null") ((char? x) (string x)) ((string? x) x)
(else (jolt-str-render-one x))))
;; (Object.) — a fresh value with distinct identity (libraries use it as a lock
;; or a unique sentinel). Each call returns a new jhost so identical?/= separate.
(register-class-ctor! "Object" (lambda _ (make-jhost "object" (vector))))
(register-class-ctor! "StringBuilder"
(lambda args (make-jhost "string-builder"
;; a numeric first arg is a CAPACITY hint, not content.
@ -308,6 +312,19 @@
(cons "close" (lambda (self) (fw-flush! self) jolt-nil))
(cons "toString" (lambda (self) (fw-buf self)))))
;; a writer over a real Chez port — the values *out* / *err* hold. write/append
;; push to the port (so (.write *out* s) and (binding [*out* *err*] …) work);
;; it isn't a buffer, so toString is empty. Lets libraries that touch *out*/*err*
;; (tools.logging, selmer) compile and run.
(register-host-methods! "port-writer"
(list (cons "write" (lambda (self x) (display (writer-piece x) (vector-ref (jhost-state self) 0)) jolt-nil))
(cons "append" (lambda (self x) (display (render-piece x) (vector-ref (jhost-state self) 0)) self))
(cons "flush" (lambda (self) (flush-output-port (vector-ref (jhost-state self) 0)) jolt-nil))
(cons "close" (lambda (self) jolt-nil))
(cons "toString" (lambda (self) ""))))
(def-var! "clojure.core" "*out*" (make-jhost "port-writer" (vector (current-output-port))))
(def-var! "clojure.core" "*err*" (make-jhost "port-writer" (vector (current-error-port))))
;; ---- java.util.HashMap ------------------------------------------------------
;; A mutable map keyed by jolt values (jolt-hash / jolt=2). State #(chez-hashtable).
;; Constructors: () | (capacity) | (capacity load-factor) [sizing args ignored] |
@ -360,7 +377,14 @@
;; ---- 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))))
;; src is a String or a char[] ((StringReader. (char-array s)) — selmer's parser
;; reads templates this way); a char-array becomes the string of its chars.
(lambda (src . _)
(make-jhost "string-reader"
(vector (cond ((string? src) src)
((jolt-array? src) (apply string-append (map jolt-str-render-one (seq->list (jolt-seq src)))))
(else (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))

View file

@ -263,4 +263,15 @@
(def-var! "clojure.java.io" "writer" jolt-io-writer)
(def-var! "clojure.java.io" "input-stream" jolt-io-reader)
(def-var! "clojure.java.io" "output-stream" jolt-io-writer)
;; resource: jolt has no classpath, so a named resource is resolved against the
;; loader's source roots (a project's :paths, e.g. "resources"). Returns a File
;; (slurp/reader-able) for the first match, else nil. get-source-roots is the
;; loader's accessor (loader.ss), resolved at call time — the runtime CLI loads it.
(define (jolt-io-resource name)
(let ((nm (jolt-str-render-one name)))
(let loop ((roots (get-source-roots)))
(cond ((null? roots) jolt-nil)
((file-exists? (string-append (car roots) "/" nm)) (make-jfile (string-append (car roots) "/" nm)))
(else (loop (cdr roots)))))))
(def-var! "clojure.java.io" "resource" jolt-io-resource)
(def-var! "clojure.java.io" "as-url" (lambda (x) (if (and (jhost? x) (string=? (jhost-tag x) "url")) x (make-url (jolt-str-render-one x)))))

View file

@ -68,10 +68,14 @@
((string=? (keyword-t-name k) "as")
(when (symbol-t? v) (chez-register-alias! cns (symbol-t-name v) target)))
((string=? (keyword-t-name k) "refer")
(when (pvec? v)
(for-each (lambda (n)
(when (symbol-t? n) (chez-register-refer! cns (symbol-t-name n) target)))
(seq->list v)))))))
(cond
;; :refer :all — bring in every public var (require :refer :all)
((and (keyword? v) (string=? (keyword-t-name v) "all"))
(chez-register-refer-all! cns target))
((pvec? v)
(for-each (lambda (n)
(when (symbol-t? n) (chez-register-refer! cns (symbol-t-name n) target)))
(seq->list v))))))))
(loop (cddr xs))))))))
;; a namespace designator -> its name string (a jns or a symbol; the corpus never

View file

@ -266,6 +266,15 @@
(let ((v (jolt-first s))) (jiterator-cur-set! obj (jolt-rest s)) v))))
(else (error #f (string-append "No method " method-name " on Iterator")))))
((string=? method-name "iterator") (make-jiterator (jolt-seq obj)))
;; java.lang.Character interop: (.toString \+) -> "+", etc.
((char? obj)
(cond ((string=? method-name "toString") (string obj))
((string=? method-name "charValue") obj)
((string=? method-name "hashCode") (char->integer obj))
((string=? method-name "equals") (and (pair? rest) (char? (car rest)) (char=? obj (car rest))))
((string=? method-name "compareTo")
(let ((o (car rest))) (cond ((char<? obj o) -1) ((char>? obj o) 1) (else 0))))
(else (error #f (string-append "No method " method-name " on char")))))
(else (error #f (string-append "No method " method-name " for value: "
(jolt-pr-str obj)))))))

View file

@ -42,9 +42,12 @@
;; ORIGINAL source is kept for printing; only the compiled pattern is translated.
(define (prop-class name)
(cond
;; L/Alpha: ASCII letters + any non-ASCII codepoint (UTF-8 high
;; bytes count as letters, so ^\p{L}+$ accepts accented words). N/Z stay ASCII-only.
((or (string=? name "L") (string=? name "Alpha")) "a-zA-Z\\x80-\\x{10FFFF}")
;; L/Alpha: ASCII letters + non-ASCII up to just below the UTF-16 surrogate gap
;; (D800). This covers essentially every real letter (Latin/Greek/Cyrillic/CJK/…
;; live below D800); the supplementary planes above it are rare and a range that
;; reaches them makes irregex's char-set construction call integer->char on a
;; surrogate and crash. N/Z stay ASCII-only.
((or (string=? name "L") (string=? name "Alpha")) "a-zA-Z\\x80-\\x{D7FF}")
((string=? name "Lu") "A-Z")
((string=? name "Ll") "a-z")
((or (string=? name "N") (string=? name "Nd") (string=? name "Digit")) "0-9")