Fix seven more JVM divergences (rewrite-clj full suite)
Running the whole rewrite-clj test suite (159 tests) surfaced seven more bugs; with these it passes 3377/0/0. Each is a general jolt/JVM divergence: - *out* was pinned to the startup stdout port, so (.write *out* …) escaped a with-out-str capture (z/print writes via *out*). It now resolves the live current-output-port, like print/__write, so a redirect is seen. - nth / assoc past the end of a vector or seq threw a bare Chez error (class :object). Throw IndexOutOfBoundsException, matching the JVM. - A number's .toString(radix) ignored the base. Render in the base, lowercase (rewrite-clj rebuilds 0xff / 0377 / 2r1001 through it). - A required namespace's own :as aliases leaked into its requirer: the loaded ns form compiles while (chez-current-ns) is still the requirer, so ce-scan-requires! registered the loaded ns's aliases under the wrong ns and clobbered a same-named alias there. Register an (ns NAME …) form's aliases under NAME. - A quoted collection dropped its metadata; now it keeps USER metadata (drops the reader's :line/:column/:file), like a Clojure quoted constant. - enumeration-seq only did (seq e); it now drives a java.util.Enumeration through hasMoreElements/nextElement, and StringTokenizer implements them. Regressions: corpus rows (with-out-str/*out*, nth/assoc bounds, toString radix, quote metadata, enumeration-seq) certified against JVM; a smoke fixture for the alias leak (a required ns's alias must not leak). tools.reader + rewrite-clj added to docs/libraries.md. make test green.
This commit is contained in:
parent
53112d06fb
commit
9bcac13fd2
15 changed files with 999 additions and 906 deletions
|
|
@ -130,7 +130,7 @@
|
|||
(vec-set (pvec-tail p) (fxand i pv-mask) x) #f)
|
||||
(mk-pvec cnt (pvec-shift p)
|
||||
(pv-assoc-trie (pvec-shift p) (pvec-root p) i x) (pvec-tail p) #f)))
|
||||
(else (error 'assoc "vector index out of bounds")))))
|
||||
(else (jolt-throw (jolt-host-throwable "java.lang.IndexOutOfBoundsException" "vector index out of bounds"))))))
|
||||
(define (pvec-peek p)
|
||||
(let ((n (pvec-cnt p))) (if (fx=? n 0) jolt-nil (pvec-nth-d p (fx- n 1) jolt-nil))))
|
||||
;; pop the last trie chunk back into the tail; #f means the subtree emptied.
|
||||
|
|
@ -449,9 +449,9 @@
|
|||
(let ((i (->idx i)))
|
||||
(cond ((pvec? coll) (let ((v (pvec-v coll)))
|
||||
(if (and (fx>=? i 0) (fx<? i (vector-length v))) (vector-ref v i)
|
||||
(error 'nth "index out of bounds"))))
|
||||
(jolt-throw (jolt-host-throwable "java.lang.IndexOutOfBoundsException" "index out of bounds")))))
|
||||
((string? coll) (if (and (fx>=? i 0) (fx<? i (string-length coll))) (string-ref coll i)
|
||||
(error 'nth "index out of bounds")))
|
||||
(jolt-throw (jolt-host-throwable "java.lang.IndexOutOfBoundsException" "index out of bounds"))))
|
||||
((or (cseq? coll) (empty-list-t? coll)) (seq-nth coll i #f jolt-nil))
|
||||
((rec-coll-method coll "nth") => (lambda (m) (jolt-invoke m coll i)))
|
||||
(else (error 'nth "unsupported collection")))))
|
||||
|
|
|
|||
|
|
@ -111,6 +111,16 @@
|
|||
(let ((scv (var-deref "jolt.backend-scheme" "set-var-cache!")))
|
||||
(when (procedure? scv) (scv #t)))
|
||||
|
||||
;; (with-meta sym m) -> sym, else x — an (ns ^:no-doc name …) yields the name with
|
||||
;; reader metadata as a with-meta form; strip it to read the bare ns symbol.
|
||||
(define (ce-unwrap-meta x)
|
||||
(if (and (cseq? x) (cseq-list? x))
|
||||
(let ((items (seq->list x)))
|
||||
(if (and (pair? items) (symbol-t? (car items))
|
||||
(string=? (symbol-t-name (car items)) "with-meta") (pair? (cdr items)))
|
||||
(cadr items) x))
|
||||
x))
|
||||
|
||||
;; (quote X) -> X, else x — unwraps a quoted require spec.
|
||||
(define (ce-unquote x)
|
||||
(if (and (cseq? x) (cseq-list? x))
|
||||
|
|
@ -135,14 +145,22 @@
|
|||
;; (require spec...) / (use spec...) — specs are quoted
|
||||
((and hn (or (string=? hn "require") (string=? hn "use")))
|
||||
(for-each (lambda (a) (chez-register-spec! ns (ce-unquote a))) (cdr items)))
|
||||
;; (ns name (:require [a :as x]) ...) — clause specs are literal
|
||||
;; (ns name (:require [a :as x]) ...) — clause specs are literal. Register
|
||||
;; the aliases under NAME (the ns being defined), not the passed `ns`:
|
||||
;; when a file is loaded its ns form compiles while (chez-current-ns) is
|
||||
;; still the requiring ns, so using `ns` would leak the loaded ns's
|
||||
;; aliases into its requirer and clobber a same-named alias there
|
||||
;; (rewrite-clj.zip.base's [node.protocols :as node] over the caller's node).
|
||||
((and hn (string=? hn "ns"))
|
||||
(for-each (lambda (clause)
|
||||
(when (and (cseq? clause) (cseq-list? clause))
|
||||
(let ((cl (seq->list clause)))
|
||||
(when (ce-clause-require? cl)
|
||||
(for-each (lambda (spec) (chez-register-spec! ns spec)) (cdr cl))))))
|
||||
(if (pair? (cdr items)) (cddr items) '())))
|
||||
(let ((ns-name (if (and (pair? (cdr items)) (symbol-t? (ce-unwrap-meta (cadr items))))
|
||||
(symbol-t-name (ce-unwrap-meta (cadr items)))
|
||||
ns)))
|
||||
(for-each (lambda (clause)
|
||||
(when (and (cseq? clause) (cseq-list? clause))
|
||||
(let ((cl (seq->list clause)))
|
||||
(when (ce-clause-require? cl)
|
||||
(for-each (lambda (spec) (chez-register-spec! ns-name spec)) (cdr cl))))))
|
||||
(if (pair? (cdr items)) (cddr items) '()))))
|
||||
(else (for-each (lambda (x) (ce-scan-requires! x ns)) items))))))))
|
||||
|
||||
;; Already-read FORM -> Scheme source string (analyze -> emit on Chez).
|
||||
|
|
|
|||
|
|
@ -176,14 +176,26 @@
|
|||
;; 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.
|
||||
;; *out*/*err* resolve their port LIVE — 'out -> (current-output-port), 'err ->
|
||||
;; (current-error-port) — so a (.write *out* …) / (.flush *out*) follows a
|
||||
;; with-out-str redirect (with-output-to-string rebinds current-output-port) the
|
||||
;; same way print/__write do. Storing the startup port instead pinned *out* to the
|
||||
;; real stdout, so rewrite-clj's (z/print) — which writes via *out* — escaped the
|
||||
;; capture. A stored port object (should any other code make a port-writer) is used
|
||||
;; as-is.
|
||||
(define (port-writer-port self)
|
||||
(let ((p (vector-ref (jhost-state self) 0)))
|
||||
(cond ((eq? p 'out) (current-output-port))
|
||||
((eq? p 'err) (current-error-port))
|
||||
(else p))))
|
||||
(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 . rest) (display (append-text x rest) (vector-ref (jhost-state self) 0)) self))
|
||||
(cons "flush" (lambda (self) (flush-output-port (vector-ref (jhost-state self) 0)) jolt-nil))
|
||||
(list (cons "write" (lambda (self x) (display (writer-piece x) (port-writer-port self)) jolt-nil))
|
||||
(cons "append" (lambda (self x . rest) (display (append-text x rest) (port-writer-port self)) self))
|
||||
(cons "flush" (lambda (self) (flush-output-port (port-writer-port self)) 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))))
|
||||
(def-var! "clojure.core" "*out*" (make-jhost "port-writer" (vector 'out)))
|
||||
(def-var! "clojure.core" "*err*" (make-jhost "port-writer" (vector 'err)))
|
||||
|
||||
;; PrintWriter — a thin wrapper over a target writer. write/append/print forward
|
||||
;; the rendered text to the target. clojure.data.json's pretty printer builds
|
||||
|
|
@ -443,7 +455,15 @@
|
|||
(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")))))))
|
||||
(error #f "NoSuchElementException")))))
|
||||
;; StringTokenizer implements java.util.Enumeration — enumeration-seq drives
|
||||
;; it through these, so alias them onto the token methods.
|
||||
(cons "hasMoreElements" (lambda (self) (< (vector-ref (jhost-state self) 1) (length (vector-ref (jhost-state self) 0)))))
|
||||
(cons "nextElement" (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. bytes [charset]) decodes bytes (a bytevector OR a jolt byte-array)
|
||||
|
|
|
|||
|
|
@ -117,7 +117,12 @@
|
|||
((string=? method "longValue") (->num (jnum->exact n)))
|
||||
((string=? method "doubleValue") (->num n))
|
||||
((string=? method "floatValue") (->num n))
|
||||
((string=? method "toString") (jolt-num->string n))
|
||||
;; .toString(radix) — BigInteger/Integer render in a base, lowercase like the
|
||||
;; JVM (rewrite-clj's integer node reconstructs 0xff / 0377 / 2r1001 this way).
|
||||
((string=? method "toString")
|
||||
(if (pair? args)
|
||||
(string-downcase (number->string (jnum->exact n) (jnum->exact (car args))))
|
||||
(jolt-num->string n)))
|
||||
((string=? method "hashCode") (->num (jnum->exact n)))
|
||||
;; Double/Float .isNaN / .isInfinite (a non-flonum is neither).
|
||||
((string=? method "isNaN") (and (flonum? n) (not (= n n))))
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -152,9 +152,9 @@
|
|||
(if (jolt-nil? s) last (loop (jolt-seq (seq-more s)) (seq-first s)))))
|
||||
;; nth over a seq (walks; forces lazily). default? selects the 3-arg behavior.
|
||||
(define (seq-nth coll i default? d)
|
||||
(if (fx<? i 0) (if default? d (error 'nth "index out of bounds"))
|
||||
(if (fx<? i 0) (if default? d (jolt-throw (jolt-host-throwable "java.lang.IndexOutOfBoundsException" "index out of bounds")))
|
||||
(let loop ((s (jolt-seq coll)) (i i))
|
||||
(cond ((jolt-nil? s) (if default? d (error 'nth "index out of bounds")))
|
||||
(cond ((jolt-nil? s) (if default? d (jolt-throw (jolt-host-throwable "java.lang.IndexOutOfBoundsException" "index out of bounds"))))
|
||||
((fx=? i 0) (seq-first s))
|
||||
(else (loop (jolt-seq (seq-more s)) (fx- i 1)))))))
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,17 @@ else
|
|||
fails=$((fails + 1))
|
||||
fi
|
||||
|
||||
# A required namespace's own :as aliases must not leak into the requirer: fix.main
|
||||
# aliases clojure.string as ss and requires fix.lib (which aliases clojure.set as
|
||||
# ss); (ss/upper-case "hi") in main must stay clojure.string -> "HI #{1 2}".
|
||||
al_out="$(JOLT_PWD="$root/test/chez/alias-leak-app" bin/joltc run -m fix.main 2>/dev/null | tail -1)"
|
||||
if [ "$al_out" = "HI #{1 2}" ]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
echo " FAIL: a loaded ns's alias leaked into its requirer — got \`$al_out\`, want \`HI #{1 2}\`"
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
|
||||
# Unit-checks the REPL read-until-complete predicate over balanced/unbalanced,
|
||||
# string, comment and regex-literal inputs. A multi-form `joltc run` so jolt.main
|
||||
# is loaded and its private var resolves; the file self-checks and prints a sentinel.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue