Strict reader tokens; edn mode with the reference's error contracts
The reader now rejects what the JVM reader rejects: a token that starts like a number but doesn't parse is NumberFormatException (1a, 08, 0x2g, 2r2 — never a symbol); ratio parts are digit runs (1/-1 invalid) with a zero denominator throwing ArithmeticException; empty ns/name parts are invalid tokens (:, ::, foo/, /foo) while /, ns//, and :/ stay valid; duplicate map keys and set elements throw at read; unsupported string escapes and octal escapes past \377 throw; a stray close delimiter is 'Unmatched delimiter'; \r ends line comments. #inst validates its calendar fields progressively (leap years included) and #uuid demands canonical hex. 1-arg symbol splits its ns at the FIRST slash (Symbol.intern): (symbol "foo/bar/baz") is foo/"bar/baz". clojure.edn gets its own strict seam (__read-form-edn): auto-resolved keywords are invalid there, every #_ discarded form validates through the same :readers/:default pipeline (an unreadable tagged element throws even when discarded), built-in tags win over :default, M literals construct BigDecimals, lists satisfy list?, and EOF honors :eof — an opts map without :eof makes end-of-input an error. clojure.edn-test.read-string goes 246 pass / 46 fail / 5 errors -> 297/0/0 (fully clean). cts baseline 5904 -> 5955 pass, 23 errors, 56 baselined namespaces. 9 JVM-certified corpus rows; reader spec section.
This commit is contained in:
parent
95186a6782
commit
44d4875a24
8 changed files with 1006 additions and 781 deletions
|
|
@ -225,3 +225,31 @@ reader functions are the deliberate exception, S20). Forms read identically
|
||||||
whether or not they will be evaluated; `read-string` of any printable value
|
whether or not they will be evaluated; `read-string` of any printable value
|
||||||
`v` followed by evaluation yields a value equal to `v` for the
|
`v` followed by evaluation yields a value equal to `v` for the
|
||||||
self-evaluating types (§4 print/read round-trip contract).
|
self-evaluating types (§4 print/read round-trip contract).
|
||||||
|
|
||||||
|
## Strict tokens and edn mode
|
||||||
|
|
||||||
|
The reader rejects what the reference rejects (corpus `edn / strictness`,
|
||||||
|
`reader / strict tokens`):
|
||||||
|
|
||||||
|
- A token that starts like a number but doesn't parse as one is
|
||||||
|
NumberFormatException, never a symbol: `1a`, `08` (a leading zero demands
|
||||||
|
octal digits; `042` is 34), `0x2g`, `2r2`. A ratio's parts are plain digit
|
||||||
|
runs (`1/-1` is invalid); a zero denominator is ArithmeticException.
|
||||||
|
- Empty ns/name parts are invalid tokens: `:`, `::`, `foo/`, `/foo`, `:/foo`.
|
||||||
|
`/` (division), `ns//` and `:/` (a name of exactly `/`) are valid.
|
||||||
|
- Map literals with duplicate keys and set literals with duplicate elements
|
||||||
|
throw IllegalArgumentException at read.
|
||||||
|
- An unsupported string escape (`"\q"`) and an octal escape past `\377`
|
||||||
|
(string or `\o` char) throw. A stray close delimiter at top level is
|
||||||
|
"Unmatched delimiter". `\r` terminates a line comment like `\n`.
|
||||||
|
- `#inst` validates its calendar fields progressively (month 1–12, day valid
|
||||||
|
for the month including leap years, hour < 24, minute < 60); `#uuid`
|
||||||
|
demands canonical 8-4-4-4-12 hex.
|
||||||
|
|
||||||
|
clojure.edn adds on top of that (`__read-form-edn` seam): auto-resolved
|
||||||
|
keywords (`::k`) are invalid (no resolution context), each `#_` discarded
|
||||||
|
form is validated through the same `:readers`/`:default` pipeline (an
|
||||||
|
unreadable tagged element throws even when discarded), `M` literals
|
||||||
|
construct BigDecimals, lists satisfy `list?`, and end-of-input honors the
|
||||||
|
`:eof` option — an opts map without `:eof` makes EOF an error, while the
|
||||||
|
no-opts arity returns nil.
|
||||||
|
|
|
||||||
|
|
@ -117,20 +117,21 @@
|
||||||
(let ((a (car args)))
|
(let ((a (car args)))
|
||||||
(cond
|
(cond
|
||||||
((jolt-symbol? a) a)
|
((jolt-symbol? a) a)
|
||||||
;; (symbol "ns/name") splits the namespace at the LAST "/" (JVM
|
;; (symbol "ns/name") splits the namespace at the FIRST "/" (JVM
|
||||||
;; Symbol.intern), so (namespace (symbol "foo/bar")) => "foo". A lone "/"
|
;; Symbol.intern), so (namespace (symbol "foo/bar/baz")) => "foo" with
|
||||||
;; or a leading slash has no namespace. The no-ns sentinel is #f — matches
|
;; name "bar/baz". A lone "/" or a leading slash has no namespace. The
|
||||||
;; emit's quoted-symbol lowering (jolt-symbol #f "x"), so (= 'x (symbol
|
;; no-ns sentinel is #f — matches emit's quoted-symbol lowering
|
||||||
;; "x")) holds (jolt= compares ns with strict equal?).
|
;; (jolt-symbol #f "x"), so (= 'x (symbol "x")) holds (jolt= compares
|
||||||
|
;; ns with strict equal?).
|
||||||
((string? a)
|
((string? a)
|
||||||
(let ((slen (string-length a)))
|
(let ((slen (string-length a)))
|
||||||
(if (string=? a "/")
|
(if (string=? a "/")
|
||||||
(jolt-symbol #f "/")
|
(jolt-symbol #f "/")
|
||||||
(let loop ((i (- slen 1)))
|
(let loop ((i 1))
|
||||||
(cond ((<= i 0) (jolt-symbol #f a))
|
(cond ((>= i slen) (jolt-symbol #f a))
|
||||||
((char=? (string-ref a i) #\/)
|
((char=? (string-ref a i) #\/)
|
||||||
(jolt-symbol (substring a 0 i) (substring a (+ i 1) slen)))
|
(jolt-symbol (substring a 0 i) (substring a (+ i 1) slen)))
|
||||||
(else (loop (- i 1))))))))
|
(else (loop (+ i 1))))))))
|
||||||
((keyword? a) (jolt-symbol (keyword-t-ns a) (keyword-t-name a)))
|
((keyword? a) (jolt-symbol (keyword-t-ns a) (keyword-t-name a)))
|
||||||
;; (symbol a-var) -> the var's qualified symbol (clojure.spec.alpha/->sym).
|
;; (symbol a-var) -> the var's qualified symbol (clojure.spec.alpha/->sym).
|
||||||
((var-cell? a) (jolt-symbol (var-cell-ns a) (var-cell-name a)))
|
((var-cell? a) (jolt-symbol (var-cell-ns a) (var-cell-name a)))
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,24 @@
|
||||||
|
|
||||||
(define (rdr-digit? c) (and (char>=? c #\0) (char<=? c #\9)))
|
(define (rdr-digit? c) (and (char>=? c #\0) (char<=? c #\9)))
|
||||||
(define (rdr-octal? c) (and (char>=? c #\0) (char<=? c #\7)))
|
(define (rdr-octal? c) (and (char>=? c #\0) (char<=? c #\7)))
|
||||||
|
(define (rdr-all-digits? s from to)
|
||||||
|
(and (> to from)
|
||||||
|
(let loop ((i from))
|
||||||
|
(cond ((>= i to) #t)
|
||||||
|
((rdr-digit? (string-ref s i)) (loop (+ i 1)))
|
||||||
|
(else #f)))))
|
||||||
;; every char of s in [from,to) is an octal digit (and the span is non-empty).
|
;; every char of s in [from,to) is an octal digit (and the span is non-empty).
|
||||||
(define (rdr-all-octal? s from to)
|
(define (rdr-all-octal? s from to)
|
||||||
(and (fx<? from to)
|
(and (fx<? from to)
|
||||||
(let loop ((i from)) (cond ((fx=? i to) #t) ((rdr-octal? (string-ref s i)) (loop (fx+ i 1))) (else #f)))))
|
(let loop ((i from)) (cond ((fx=? i to) #t) ((rdr-octal? (string-ref s i)) (loop (fx+ i 1))) (else #f)))))
|
||||||
|
|
||||||
;; Advance past whitespace, commas, and ;-to-end-of-line comments.
|
;; Advance past whitespace, commas, and ;-to-end-of-line comments.
|
||||||
|
;; EDN strict mode (clojure.edn): auto-resolved keywords are invalid, and each
|
||||||
|
;; discarded (#_) form is handed to rdr-discard-cb so the edn layer validates
|
||||||
|
;; its tagged elements through :readers/:default like the JVM.
|
||||||
|
(define rdr-edn-mode (make-parameter #f))
|
||||||
|
(define rdr-discard-cb (make-parameter #f))
|
||||||
|
|
||||||
(define (rdr-skip-ws s i end)
|
(define (rdr-skip-ws s i end)
|
||||||
(let loop ((i i))
|
(let loop ((i i))
|
||||||
(cond
|
(cond
|
||||||
|
|
@ -61,7 +73,8 @@
|
||||||
((rdr-ws? (string-ref s i)) (loop (+ i 1)))
|
((rdr-ws? (string-ref s i)) (loop (+ i 1)))
|
||||||
((char=? (string-ref s i) #\;)
|
((char=? (string-ref s i) #\;)
|
||||||
(let eol ((j (+ i 1)))
|
(let eol ((j (+ i 1)))
|
||||||
(if (or (>= j end) (char=? (string-ref s j) #\newline))
|
(if (or (>= j end) (char=? (string-ref s j) #\newline)
|
||||||
|
(char=? (string-ref s j) #\return))
|
||||||
(loop j)
|
(loop j)
|
||||||
(eol (+ j 1)))))
|
(eol (+ j 1)))))
|
||||||
(else i))))
|
(else i))))
|
||||||
|
|
@ -115,12 +128,17 @@
|
||||||
(slash (rdr-string-index-char body #\/)))
|
(slash (rdr-string-index-char body #\/)))
|
||||||
(cond
|
(cond
|
||||||
;; ratio a/b -> exact rational (= JVM Ratio); reduces to an exact integer
|
;; ratio a/b -> exact rational (= JVM Ratio); reduces to an exact integer
|
||||||
;; when d divides n.
|
;; when d divides n. Both parts must be plain digit runs (1/-1 is an
|
||||||
|
;; invalid token); a zero denominator is the JVM's divide error.
|
||||||
(slash
|
(slash
|
||||||
(let ((n (string->number (substring body 0 slash)))
|
(let ((ns (substring body 0 slash))
|
||||||
(d (string->number (substring body (+ slash 1) blen))))
|
(ds (substring body (+ slash 1) blen)))
|
||||||
(and (integer? n) (integer? d) (not (= d 0))
|
(and (rdr-all-digits? ns 0 (string-length ns))
|
||||||
(* sign (/ n d)))))
|
(rdr-all-digits? ds 0 (string-length ds))
|
||||||
|
(let ((n (string->number ns)) (d (string->number ds)))
|
||||||
|
(when (= d 0)
|
||||||
|
(jolt-throw (jolt-host-throwable "java.lang.ArithmeticException" "Divide by zero")))
|
||||||
|
(* sign (/ n d))))))
|
||||||
;; hex 0x..
|
;; hex 0x..
|
||||||
((and (>= blen 2) (char=? (string-ref body 0) #\0)
|
((and (>= blen 2) (char=? (string-ref body 0) #\0)
|
||||||
(or (char=? (string-ref body 1) #\x) (char=? (string-ref body 1) #\X)))
|
(or (char=? (string-ref body 1) #\x) (char=? (string-ref body 1) #\X)))
|
||||||
|
|
@ -139,6 +157,11 @@
|
||||||
;; elsewhere or fall through (a non-octal digit fails rdr-all-octal?).
|
;; elsewhere or fall through (a non-octal digit fails rdr-all-octal?).
|
||||||
((and (>= blen 2) (char=? (string-ref body 0) #\0) (rdr-all-octal? body 1 blen))
|
((and (>= blen 2) (char=? (string-ref body 0) #\0) (rdr-all-octal? body 1 blen))
|
||||||
(let ((o (rdr-parse-radix (substring body 1 blen) 8))) (and o (* sign o))))
|
(let ((o (rdr-parse-radix (substring body 1 blen) 8))) (and o (* sign o))))
|
||||||
|
;; a leading zero on a plain multi-digit integer is invalid (the octal
|
||||||
|
;; branch above accepted real octals; 08/09 match the JVM's trailing
|
||||||
|
;; "invalid number" alternative)
|
||||||
|
((and (>= blen 2) (char=? (string-ref body 0) #\0) (rdr-all-digits? body 1 blen))
|
||||||
|
#f)
|
||||||
;; bigint suffix N
|
;; bigint suffix N
|
||||||
((and (> blen 1) (char=? (string-ref body (- blen 1)) #\N))
|
((and (> blen 1) (char=? (string-ref body (- blen 1)) #\N))
|
||||||
(let ((n (string->number (substring body 0 (- blen 1)))))
|
(let ((n (string->number (substring body 0 (- blen 1)))))
|
||||||
|
|
@ -190,7 +213,10 @@
|
||||||
(let oct ((j (+ i 1)) (val 0) (cnt 0))
|
(let oct ((j (+ i 1)) (val 0) (cnt 0))
|
||||||
(if (and (fx<? cnt 3) (fx<? j end) (rdr-octal? (string-ref s j)))
|
(if (and (fx<? cnt 3) (fx<? j end) (rdr-octal? (string-ref s j)))
|
||||||
(oct (fx+ j 1) (fx+ (fx* val 8) (fx- (char->integer (string-ref s j)) 48)) (fx+ cnt 1))
|
(oct (fx+ j 1) (fx+ (fx* val 8) (fx- (char->integer (string-ref s j)) 48)) (fx+ cnt 1))
|
||||||
(loop j (cons (integer->char val) acc)))))
|
(begin
|
||||||
|
(when (> val 255)
|
||||||
|
(jolt-throw (jolt-ex-info "Octal escape sequence must be in range [0, 377]" empty-pmap)))
|
||||||
|
(loop j (cons (integer->char val) acc))))))
|
||||||
((#\u)
|
((#\u)
|
||||||
(let-values (((cp j) (rdr-hex->int s (+ i 2) 4)))
|
(let-values (((cp j) (rdr-hex->int s (+ i 2) 4)))
|
||||||
;; A \u escape is a UTF-16 code unit. jolt chars are Unicode scalars,
|
;; A \u escape is a UTF-16 code unit. jolt chars are Unicode scalars,
|
||||||
|
|
@ -208,7 +234,8 @@
|
||||||
(loop j (cons #\xFFFD acc)))))
|
(loop j (cons #\xFFFD acc)))))
|
||||||
((and (fx>=? cp #xD800) (fx<=? cp #xDFFF)) (loop j (cons #\xFFFD acc)))
|
((and (fx>=? cp #xD800) (fx<=? cp #xDFFF)) (loop j (cons #\xFFFD acc)))
|
||||||
(else (loop j (cons (integer->char cp) acc))))))
|
(else (loop j (cons (integer->char cp) acc))))))
|
||||||
(else (loop (+ i 2) (cons e acc))))))
|
(else (jolt-throw (jolt-ex-info (string-append "Unsupported escape character: \\" (string e))
|
||||||
|
empty-pmap))))))
|
||||||
(else (loop (+ i 1) (cons c acc)))))))
|
(else (loop (+ i 1) (cons c acc)))))))
|
||||||
|
|
||||||
;; backslash already consumed; read a Clojure character literal.
|
;; backslash already consumed; read a Clojure character literal.
|
||||||
|
|
@ -240,7 +267,10 @@
|
||||||
((char=? (string-ref name 0) #\u)
|
((char=? (string-ref name 0) #\u)
|
||||||
(integer->char (string->number (substring name 1 (string-length name)) 16)))
|
(integer->char (string->number (substring name 1 (string-length name)) 16)))
|
||||||
((char=? (string-ref name 0) #\o)
|
((char=? (string-ref name 0) #\o)
|
||||||
(integer->char (string->number (substring name 1 (string-length name)) 8)))
|
(let ((v (string->number (substring name 1 (string-length name)) 8)))
|
||||||
|
(when (or (not v) (> v 255))
|
||||||
|
(jolt-throw (jolt-ex-info "Octal escape sequence must be in range [0, 377]" empty-pmap)))
|
||||||
|
(integer->char v)))
|
||||||
(else (jolt-throw (jolt-ex-info (string-append "Unsupported character: \\" name)
|
(else (jolt-throw (jolt-ex-info (string-append "Unsupported character: \\" name)
|
||||||
empty-pmap)))))
|
empty-pmap)))))
|
||||||
|
|
||||||
|
|
@ -258,14 +288,39 @@
|
||||||
(values #f tok)
|
(values #f tok)
|
||||||
(values (substring tok 0 slash) (substring tok (+ slash 1) (string-length tok))))))
|
(values (substring tok 0 slash) (substring tok (+ slash 1) (string-length tok))))))
|
||||||
|
|
||||||
|
(define (rdr-numeric-lead? tok)
|
||||||
|
(let ((len (string-length tok)))
|
||||||
|
(and (> len 0)
|
||||||
|
(let ((c0 (string-ref tok 0)))
|
||||||
|
(or (rdr-digit? c0)
|
||||||
|
(and (or (char=? c0 #\+) (char=? c0 #\-)) (> len 1)
|
||||||
|
(rdr-digit? (string-ref tok 1))))))))
|
||||||
|
(define (rdr-invalid-token tok)
|
||||||
|
(jolt-throw (jolt-host-throwable "java.lang.RuntimeException"
|
||||||
|
(string-append "Invalid token: " tok))))
|
||||||
(define (rdr-token->value tok)
|
(define (rdr-token->value tok)
|
||||||
(let ((n (rdr-try-number tok)))
|
(let ((n (rdr-try-number tok)))
|
||||||
(cond
|
(cond
|
||||||
(n n)
|
(n n)
|
||||||
|
;; a token that starts like a number but doesn't parse as one is an
|
||||||
|
;; invalid number (1a, 08, 0x2g, 2r2), never a symbol — like the JVM.
|
||||||
|
((rdr-numeric-lead? tok)
|
||||||
|
(jolt-throw (jolt-host-throwable "java.lang.NumberFormatException"
|
||||||
|
(string-append "Invalid number: " tok))))
|
||||||
((string=? tok "nil") jolt-nil)
|
((string=? tok "nil") jolt-nil)
|
||||||
((string=? tok "true") #t)
|
((string=? tok "true") #t)
|
||||||
((string=? tok "false") #f)
|
((string=? tok "false") #f)
|
||||||
(else (let-values (((ns name) (rdr-sym-parts tok))) (jolt-symbol ns name))))))
|
(else
|
||||||
|
(let ((len (string-length tok)))
|
||||||
|
;; a lone "/" is the division symbol, and "ns//" names it in a
|
||||||
|
;; namespace (clojure.core//); otherwise a leading or trailing slash
|
||||||
|
;; leaves an empty ns/name part — an invalid token.
|
||||||
|
(when (and (> len 1)
|
||||||
|
(or (char=? (string-ref tok 0) #\/)
|
||||||
|
(and (char=? (string-ref tok (- len 1)) #\/)
|
||||||
|
(not (and (> len 2) (char=? (string-ref tok (- len 2)) #\/))))))
|
||||||
|
(rdr-invalid-token tok))
|
||||||
|
(let-values (((ns name) (rdr-sym-parts tok))) (jolt-symbol ns name)))))))
|
||||||
|
|
||||||
;; --- collections ------------------------------------------------------------
|
;; --- collections ------------------------------------------------------------
|
||||||
;; Read forms until the close delimiter; returns (values reversed?-no list j).
|
;; Read forms until the close delimiter; returns (values reversed?-no list j).
|
||||||
|
|
@ -289,6 +344,14 @@
|
||||||
;; sequence in a weak side-table the host contract's form-map-pairs consults.
|
;; sequence in a weak side-table the host contract's form-map-pairs consults.
|
||||||
(define rdr-map-order (make-weak-eq-hashtable))
|
(define rdr-map-order (make-weak-eq-hashtable))
|
||||||
(define (rdr-make-map es)
|
(define (rdr-make-map es)
|
||||||
|
;; the JVM reader rejects duplicate literal keys before building the map
|
||||||
|
(let dupchk ((kvs es) (seen empty-pset))
|
||||||
|
(when (pair? kvs)
|
||||||
|
(let ((k (car kvs)))
|
||||||
|
(when (jolt-truthy? (jolt-contains? seen k))
|
||||||
|
(jolt-throw (jolt-host-throwable "java.lang.IllegalArgumentException"
|
||||||
|
(string-append "Duplicate key: " (jolt-pr-str k)))))
|
||||||
|
(dupchk (cddr kvs) (pset-conj seen k)))))
|
||||||
(let ((m (apply jolt-hash-map es)))
|
(let ((m (apply jolt-hash-map es)))
|
||||||
(when (pair? es) (hashtable-set! rdr-map-order m es))
|
(when (pair? es) (hashtable-set! rdr-map-order m es))
|
||||||
m))
|
m))
|
||||||
|
|
@ -548,8 +611,12 @@
|
||||||
(let-values (((src j) (rdr-read-regex s (+ i 1) end)))
|
(let-values (((src j) (rdr-read-regex s (+ i 1) end)))
|
||||||
(values (jolt-re-pattern src) j)))
|
(values (jolt-re-pattern src) j)))
|
||||||
((char=? c #\_) ; #_ discard the next form
|
((char=? c #\_) ; #_ discard the next form
|
||||||
(let-values (((_ j) (rdr-read-form s (+ i 1) end)))
|
(let-values (((d j) (rdr-read-form s (+ i 1) end)))
|
||||||
(when (rdr-eof? _) (jolt-throw (jolt-ex-info "EOF after #_" empty-pmap)))
|
(when (rdr-eof? d) (jolt-throw (jolt-ex-info "EOF after #_" empty-pmap)))
|
||||||
|
;; edn validates the discarded element (its tags go through the same
|
||||||
|
;; :readers/:default pipeline; an unreadable one throws)
|
||||||
|
(let ((cb (rdr-discard-cb)))
|
||||||
|
(when cb (jolt-invoke cb d)))
|
||||||
(rdr-read-form s j end)))
|
(rdr-read-form s j end)))
|
||||||
((char=? c #\') ; #'x var-quote -> (var x)
|
((char=? c #\') ; #'x var-quote -> (var x)
|
||||||
(let-values (((form j) (rdr-read-form s (+ i 1) end)))
|
(let-values (((form j) (rdr-read-form s (+ i 1) end)))
|
||||||
|
|
@ -601,6 +668,17 @@
|
||||||
(let ((auto? (and (< i end) (char=? (string-ref s i) #\:))))
|
(let ((auto? (and (< i end) (char=? (string-ref s i) #\:))))
|
||||||
(let ((i (if auto? (+ i 1) i)))
|
(let ((i (if auto? (+ i 1) i)))
|
||||||
(let-values (((tok j) (rdr-read-token s i end)))
|
(let-values (((tok j) (rdr-read-token s i end)))
|
||||||
|
(let ((len (string-length tok)))
|
||||||
|
;; ":" and "::" alone, a leading or trailing slash (a name of exactly
|
||||||
|
;; "/" is fine, :ns//), or an auto-resolved keyword in edn (no
|
||||||
|
;; resolution context) are invalid tokens.
|
||||||
|
(when (or (= len 0)
|
||||||
|
(and (> len 1) (char=? (string-ref tok 0) #\/))
|
||||||
|
(and (> len 1) (char=? (string-ref tok (- len 1)) #\/)
|
||||||
|
(not (and (> len 2) (char=? (string-ref tok (- len 2)) #\/)))))
|
||||||
|
(rdr-invalid-token (string-append (if auto? "::" ":") tok)))
|
||||||
|
(when (and auto? (rdr-edn-mode))
|
||||||
|
(rdr-invalid-token (string-append "::" tok))))
|
||||||
(let-values (((ns name) (rdr-sym-parts tok)))
|
(let-values (((ns name) (rdr-sym-parts tok)))
|
||||||
(if auto?
|
(if auto?
|
||||||
(let* ((cur (chez-current-ns))
|
(let* ((cur (chez-current-ns))
|
||||||
|
|
@ -735,15 +813,76 @@
|
||||||
(let ((v (var-deref "clojure.core" "*default-data-reader-fn*")))
|
(let ((v (var-deref "clojure.core" "*default-data-reader-fn*")))
|
||||||
(and (not (jolt-nil? v)) (procedure? v) v))))
|
(and (not (jolt-nil? v)) (procedure? v) v))))
|
||||||
|
|
||||||
|
;; strict #inst validation: RFC-3339 calendar fields must be real (month 1-12,
|
||||||
|
;; day valid for the month incl. leap years, hour < 24, minute/second < 60).
|
||||||
|
(define (rdr-2dig s i)
|
||||||
|
(and (< (+ i 1) (string-length s))
|
||||||
|
(rdr-digit? (string-ref s i)) (rdr-digit? (string-ref s (+ i 1)))
|
||||||
|
(+ (* 10 (- (char->integer (string-ref s i)) 48))
|
||||||
|
(- (char->integer (string-ref s (+ i 1))) 48))))
|
||||||
|
(define (rdr-leap? y) (and (= 0 (modulo y 4)) (or (not (= 0 (modulo y 100))) (= 0 (modulo y 400)))))
|
||||||
|
(define (rdr-inst-throw s)
|
||||||
|
(jolt-throw (jolt-host-throwable "java.lang.RuntimeException"
|
||||||
|
(string-append "Unrecognized date/time syntax: " s))))
|
||||||
|
(define (rdr-validate-inst! s)
|
||||||
|
;; progressive RFC-3339 like clojure.instant: yyyy[-MM[-dd[Thh[:mm[:ss[.f]]]]]]
|
||||||
|
;; with an optional Z/±hh:mm offset; each present field must be in range
|
||||||
|
;; (months 1-12, day valid for the month incl. leap years, hour < 24, min < 60).
|
||||||
|
(let* ((len (string-length s))
|
||||||
|
(y (and (>= len 4) (rdr-all-digits? s 0 4) (string->number (substring s 0 4)))))
|
||||||
|
(unless y (rdr-inst-throw s))
|
||||||
|
(when (>= len 5)
|
||||||
|
(unless (char=? (string-ref s 4) #\-) (rdr-inst-throw s))
|
||||||
|
(let ((mo (rdr-2dig s 5)))
|
||||||
|
(unless (and mo (>= mo 1) (<= mo 12)) (rdr-inst-throw s))
|
||||||
|
(when (>= len 8)
|
||||||
|
(unless (char=? (string-ref s 7) #\-) (rdr-inst-throw s))
|
||||||
|
(let ((d (rdr-2dig s 8)))
|
||||||
|
(unless (and d (>= d 1)
|
||||||
|
(<= d (vector-ref (if (rdr-leap? y)
|
||||||
|
'#(31 29 31 30 31 30 31 31 30 31 30 31)
|
||||||
|
'#(31 28 31 30 31 30 31 31 30 31 30 31))
|
||||||
|
(- mo 1))))
|
||||||
|
(rdr-inst-throw s))
|
||||||
|
(when (>= len 11)
|
||||||
|
(unless (char=? (string-ref s 10) #\T) (rdr-inst-throw s))
|
||||||
|
(let ((h (rdr-2dig s 11)))
|
||||||
|
(unless (and h (<= h 23)) (rdr-inst-throw s))
|
||||||
|
(when (>= len 14)
|
||||||
|
(when (char=? (string-ref s 13) #\:)
|
||||||
|
(let ((mi (rdr-2dig s 14)))
|
||||||
|
(unless (and mi (<= mi 59)) (rdr-inst-throw s)))))))))))))
|
||||||
|
;; strict #uuid: canonical 8-4-4-4-12 hex groups.
|
||||||
|
(define (rdr-validate-uuid! s)
|
||||||
|
(define (hexrun? from to)
|
||||||
|
(let loop ((i from))
|
||||||
|
(cond ((>= i to) #t)
|
||||||
|
((let ((c (char-downcase (string-ref s i))))
|
||||||
|
(or (rdr-digit? c) (and (char>=? c #\a) (char<=? c #\f))))
|
||||||
|
(loop (+ i 1)))
|
||||||
|
(else #f))))
|
||||||
|
(unless (and (= (string-length s) 36)
|
||||||
|
(char=? (string-ref s 8) #\-) (char=? (string-ref s 13) #\-)
|
||||||
|
(char=? (string-ref s 18) #\-) (char=? (string-ref s 23) #\-)
|
||||||
|
(hexrun? 0 8) (hexrun? 9 13) (hexrun? 14 18) (hexrun? 19 23) (hexrun? 24 36))
|
||||||
|
(jolt-throw (jolt-host-throwable "java.lang.IllegalArgumentException"
|
||||||
|
(string-append "Invalid UUID string: " s)))))
|
||||||
|
|
||||||
;; read-string / read data seam: construct the value for a #tag literal. #inst,
|
;; read-string / read data seam: construct the value for a #tag literal. #inst,
|
||||||
;; #uuid and #"regex" are built in; any other tag is applied from *data-readers*,
|
;; #uuid and #"regex" are built in; any other tag is applied from *data-readers*,
|
||||||
;; then *default-data-reader-fn*. An unregistered tag with no default handler stays
|
;; then *default-data-reader-fn*. An unregistered tag with no default handler stays
|
||||||
;; a tagged FORM (lenient — clojure.edn raises instead).
|
;; a tagged FORM (lenient — clojure.edn raises instead).
|
||||||
(define (rdr-construct-tag tag inner)
|
(define (rdr-construct-tag tag inner)
|
||||||
(cond
|
(cond
|
||||||
((eq? tag (keyword #f "#inst")) (jolt-inst-from-string inner))
|
((eq? tag (keyword #f "#inst"))
|
||||||
((eq? tag (keyword #f "#uuid")) (jolt-uuid-from-string inner))
|
(when (string? inner) (rdr-validate-inst! inner))
|
||||||
|
(jolt-inst-from-string inner))
|
||||||
|
((eq? tag (keyword #f "#uuid"))
|
||||||
|
(when (string? inner) (rdr-validate-uuid! inner))
|
||||||
|
(jolt-uuid-from-string inner))
|
||||||
((eq? tag (keyword #f "regex")) (jolt-re-pattern inner))
|
((eq? tag (keyword #f "regex")) (jolt-re-pattern inner))
|
||||||
|
;; the M-literal form: construct the BigDecimal from its numeric text
|
||||||
|
((eq? tag (keyword #f "bigdec")) (jolt-bigdec-from-string inner))
|
||||||
(else (let ((fn (rdr-data-reader-fn tag)))
|
(else (let ((fn (rdr-data-reader-fn tag)))
|
||||||
(if fn (jolt-invoke fn inner)
|
(if fn (jolt-invoke fn inner)
|
||||||
(let ((dfn (rdr-default-data-reader-fn)))
|
(let ((dfn (rdr-default-data-reader-fn)))
|
||||||
|
|
@ -765,7 +904,11 @@
|
||||||
(let ((items (jolt-get x rdr-kw-value)))
|
(let ((items (jolt-get x rdr-kw-value)))
|
||||||
(let loop ((i 0) (s empty-pset))
|
(let loop ((i 0) (s empty-pset))
|
||||||
(if (fx>=? i (pvec-count items)) s
|
(if (fx>=? i (pvec-count items)) s
|
||||||
(loop (fx+ i 1) (pset-conj s (rdr-form->data (pvec-nth-d items i jolt-nil))))))))
|
(let ((v (rdr-form->data (pvec-nth-d items i jolt-nil))))
|
||||||
|
(when (jolt-truthy? (jolt-contains? s v))
|
||||||
|
(jolt-throw (jolt-host-throwable "java.lang.IllegalArgumentException"
|
||||||
|
(string-append "Duplicate key: " (jolt-pr-str v)))))
|
||||||
|
(loop (fx+ i 1) (pset-conj s v)))))))
|
||||||
((pvec? x)
|
((pvec? x)
|
||||||
(let-values (((items changed) (rdr-conv-each (vector->list (pvec-v x)))))
|
(let-values (((items changed) (rdr-conv-each (vector->list (pvec-v x)))))
|
||||||
(if changed (apply jolt-vector items) x)))
|
(if changed (apply jolt-vector items) x)))
|
||||||
|
|
@ -790,12 +933,35 @@
|
||||||
(if (jolt-nil? m) v (jolt-with-meta v (rdr-form->data m)))))
|
(if (jolt-nil? m) v (jolt-with-meta v (rdr-form->data m)))))
|
||||||
|
|
||||||
;; --- the two host seams -----------------------------------------------------
|
;; --- the two host seams -----------------------------------------------------
|
||||||
|
;; a top-level read: a stray close delimiter is unmatched (read-seq consumes the
|
||||||
|
;; close of an open collection; anything reaching here is unbalanced input).
|
||||||
|
(define (rdr-read-top s i end)
|
||||||
|
(let ((k (rdr-skip-ws s i end)))
|
||||||
|
(when (and (< k end)
|
||||||
|
(let ((c (string-ref s k)))
|
||||||
|
(or (char=? c #\)) (char=? c #\]) (char=? c #\}))))
|
||||||
|
(jolt-throw (jolt-ex-info (string-append "Unmatched delimiter: "
|
||||||
|
(string (string-ref s k)))
|
||||||
|
empty-pmap)))
|
||||||
|
(rdr-read-form s k end)))
|
||||||
|
|
||||||
;; clojure.core/read-string: first form, or nil for blank / comment-only input
|
;; clojure.core/read-string: first form, or nil for blank / comment-only input
|
||||||
;; (parse-string wart, matched deliberately). jolt-read-form-raw keeps set FORMS
|
;; (parse-string wart, matched deliberately). jolt-read-form-raw keeps set FORMS
|
||||||
;; for the compiler spine (compile-eval); the data seam converts them to sets.
|
;; for the compiler spine (compile-eval); the data seam converts them to sets.
|
||||||
(define (jolt-read-form-raw s)
|
(define (jolt-read-form-raw s)
|
||||||
(let-values (((form j) (rdr-read-form s 0 (string-length s))))
|
(let-values (((form j) (rdr-read-top s 0 (string-length s))))
|
||||||
(if (rdr-eof? form) jolt-nil form)))
|
(if (rdr-eof? form) jolt-nil form)))
|
||||||
|
|
||||||
|
;; the edn seam: strict mode (no auto-resolved keywords), each #_ discard handed
|
||||||
|
;; to the callback for tag validation, and a distinct EOF sentinel so the edn
|
||||||
|
;; layer can honor its :eof option (nil input is a plain EOF).
|
||||||
|
(define (jolt-read-form-edn s cb)
|
||||||
|
(if (jolt-nil? s)
|
||||||
|
(keyword "jolt" "reader-eof")
|
||||||
|
(parameterize ((rdr-edn-mode #t)
|
||||||
|
(rdr-discard-cb (if (jolt-nil? cb) #f cb)))
|
||||||
|
(let-values (((form j) (rdr-read-top s 0 (string-length s))))
|
||||||
|
(if (rdr-eof? form) (keyword "jolt" "reader-eof") form)))))
|
||||||
(define (jolt-read-string s)
|
(define (jolt-read-string s)
|
||||||
(let ((form (jolt-read-form-raw s)))
|
(let ((form (jolt-read-form-raw s)))
|
||||||
(if (jolt-nil? form) form (rdr-form->data form))))
|
(if (jolt-nil? form) form (rdr-form->data form))))
|
||||||
|
|
@ -803,7 +969,7 @@
|
||||||
;; __parse-next: [form rest-of-string] or nil when only whitespace/comments left.
|
;; __parse-next: [form rest-of-string] or nil when only whitespace/comments left.
|
||||||
(define (jolt-parse-next s)
|
(define (jolt-parse-next s)
|
||||||
(let ((end (string-length s)))
|
(let ((end (string-length s)))
|
||||||
(let-values (((form j) (rdr-read-form s 0 end)))
|
(let-values (((form j) (rdr-read-top s 0 end)))
|
||||||
(if (rdr-eof? form)
|
(if (rdr-eof? form)
|
||||||
jolt-nil
|
jolt-nil
|
||||||
(jolt-vector (rdr-form->data form) (substring s j end))))))
|
(jolt-vector (rdr-form->data form) (substring s j end))))))
|
||||||
|
|
@ -812,8 +978,13 @@
|
||||||
;; is the :#name keyword the reader produced; #uuid/#inst reuse the inst-time ctors.
|
;; is the :#name keyword the reader produced; #uuid/#inst reuse the inst-time ctors.
|
||||||
(define (jolt-read-tagged tag form)
|
(define (jolt-read-tagged tag form)
|
||||||
(cond
|
(cond
|
||||||
((eq? tag (keyword #f "#uuid")) (jolt-uuid-from-string form))
|
((eq? tag (keyword #f "#uuid"))
|
||||||
((eq? tag (keyword #f "#inst")) (jolt-inst-from-string form))
|
(when (string? form) (rdr-validate-uuid! form))
|
||||||
|
(jolt-uuid-from-string form))
|
||||||
|
((eq? tag (keyword #f "#inst"))
|
||||||
|
(when (string? form) (rdr-validate-inst! form))
|
||||||
|
(jolt-inst-from-string form))
|
||||||
|
((eq? tag (keyword #f "bigdec")) (jolt-bigdec-from-string form))
|
||||||
;; No registered reader: consult *default-data-reader-fn*, else throw a clean,
|
;; No registered reader: consult *default-data-reader-fn*, else throw a clean,
|
||||||
;; catchable ex-info naming the tag, like the JVM's "No reader function for tag
|
;; catchable ex-info naming the tag, like the JVM's "No reader function for tag
|
||||||
;; foobar" (empty-pmap is a VALUE — the old (empty-pmap) applied it as a
|
;; foobar" (empty-pmap is a VALUE — the old (empty-pmap) applied it as a
|
||||||
|
|
@ -833,3 +1004,4 @@
|
||||||
;; :default (a #inst can be overridden to defer), rather than read-string building
|
;; :default (a #inst can be overridden to defer), rather than read-string building
|
||||||
;; the built-in #inst eagerly (which fails on a non-string like #inst ^:ref […]).
|
;; the built-in #inst eagerly (which fails on a non-string like #inst ^:ref […]).
|
||||||
(def-var! "clojure.core" "__read-form-raw" jolt-read-form-raw)
|
(def-var! "clojure.core" "__read-form-raw" jolt-read-form-raw)
|
||||||
|
(def-var! "clojure.core" "__read-form-edn" jolt-read-form-edn)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -16,7 +16,15 @@
|
||||||
;; Reader FORMS are detected by :jolt/type tag, never by map? — strict map?
|
;; Reader FORMS are detected by :jolt/type tag, never by map? — strict map?
|
||||||
;; (correctly) excludes tagged structs, so the old (and (map? x) ...) guard
|
;; (correctly) excludes tagged structs, so the old (and (map? x) ...) guard
|
||||||
;; would skip them.
|
;; would skip them.
|
||||||
(= :jolt/set (get x :jolt/type)) (with-meta (set (map (fn [v] (edn->value opts v)) (get x :value))) (edn->value opts (meta x)))
|
(= :jolt/set (get x :jolt/type))
|
||||||
|
(let [vs (map (fn [v] (edn->value opts v)) (get x :value))
|
||||||
|
st (set vs)]
|
||||||
|
;; duplicate literal elements are invalid edn
|
||||||
|
(when (< (count st) (count vs))
|
||||||
|
(throw (new IllegalArgumentException
|
||||||
|
(str "Duplicate key: " (pr-str (some (fn [[k n]] (when (< 1 n) k))
|
||||||
|
(frequencies vs)))))))
|
||||||
|
(with-meta st (edn->value opts (meta x))))
|
||||||
;; Tagged elements: a reader from the :readers opt wins, then the built-in
|
;; Tagged elements: a reader from the :readers opt wins, then the built-in
|
||||||
;; data readers (#uuid/#inst + registered); an unknown tag falls to the
|
;; data readers (#uuid/#inst + registered); an unknown tag falls to the
|
||||||
;; :default opt fn (called with tag and value, as in Clojure) or throws.
|
;; :default opt fn (called with tag and value, as in Clojure) or throws.
|
||||||
|
|
@ -30,6 +38,9 @@
|
||||||
custom (get (get opts :readers) tag-sym)]
|
custom (get (get opts :readers) tag-sym)]
|
||||||
(cond
|
(cond
|
||||||
custom (custom v)
|
custom (custom v)
|
||||||
|
;; the built-in edn tags win over :default (a :readers entry can
|
||||||
|
;; override them; an unknown-tag :default never sees #inst/#uuid)
|
||||||
|
(contains? #{'inst 'uuid 'bigdec} tag-sym) (__read-tagged tag v)
|
||||||
;; Clojure calls :default with the tag as a SYMBOL and the value.
|
;; Clojure calls :default with the tag as a SYMBOL and the value.
|
||||||
(get opts :default) ((get opts :default) tag-sym v)
|
(get opts :default) ((get opts :default) tag-sym v)
|
||||||
:else (__read-tagged tag v)))
|
:else (__read-tagged tag v)))
|
||||||
|
|
@ -39,25 +50,30 @@
|
||||||
;; a constructed set: recurse into its elements too, so a tagged literal
|
;; a constructed set: recurse into its elements too, so a tagged literal
|
||||||
;; inside #{…} gets the :readers/:default treatment (aero's #ref in a set).
|
;; inside #{…} gets the :readers/:default treatment (aero's #ref in a set).
|
||||||
(set? x) (with-meta (set (map (fn [v] (edn->value opts v)) x)) (edn->value opts (meta x)))
|
(set? x) (with-meta (set (map (fn [v] (edn->value opts v)) x)) (edn->value opts (meta x)))
|
||||||
(seq? x) (with-meta (map (fn [v] (edn->value opts v)) x) (edn->value opts (meta x)))
|
;; edn lists are lists (list? holds), not lazy seqs
|
||||||
|
(seq? x) (with-meta (apply list (map (fn [v] (edn->value opts v)) x)) (edn->value opts (meta x)))
|
||||||
:else x))
|
:else x))
|
||||||
|
|
||||||
;; Private helper, NOT named read-string: an unqualified (read-string …) call
|
;; Private helper, NOT named read-string: an unqualified (read-string …) call
|
||||||
;; dispatches the core read-string SPECIAL FORM (by name, regardless of ns), so
|
;; dispatches the core read-string SPECIAL FORM (by name, regardless of ns), so
|
||||||
;; the 1-arity can't delegate to the 2-arity through that name.
|
;; the 1-arity can't delegate to the 2-arity through that name.
|
||||||
(defn- read-edn [opts s]
|
(defn- read-edn [opts s]
|
||||||
(if (or (nil? s) (cstr/blank? s))
|
;; the strict edn seam: no auto-resolved keywords, invalid tokens throw, and
|
||||||
(get opts :eof nil)
|
;; each #_ discard is validated through the same :readers/:default pipeline.
|
||||||
;; read the RAW form (tagged/set literals stay forms) so edn->value applies
|
;; EOF (blank/comment-only/nil input) honors :eof; an opts map WITHOUT :eof
|
||||||
;; every #tag through :readers/:default — read-string would build the built-in
|
;; makes end-of-input an error, like the reference.
|
||||||
;; #inst/#uuid eagerly, ignoring an override and failing on a non-string form.
|
(let [v (__read-form-edn s (fn [form] (edn->value opts form) nil))]
|
||||||
(edn->value opts (__read-form-raw s))))
|
(if (= v :jolt/reader-eof)
|
||||||
|
(if (contains? opts :eof)
|
||||||
|
(get opts :eof)
|
||||||
|
(throw (ex-info "EOF while reading" {})))
|
||||||
|
(edn->value opts v))))
|
||||||
|
|
||||||
(defn read-string
|
(defn read-string
|
||||||
"Reads one object from the string s. Returns the :eof option value (default
|
"Reads one object from the string s. The no-opts arity returns nil at end of
|
||||||
nil) for nil or blank input. opts is an options map; :eof sets the value
|
input; with an opts map, :eof sets the value returned at end of input and its
|
||||||
returned at end of input."
|
absence makes end-of-input an error."
|
||||||
([s] (read-edn {} s))
|
([s] (read-edn {:eof nil} s))
|
||||||
([opts s] (read-edn opts s)))
|
([opts s] (read-edn opts s)))
|
||||||
|
|
||||||
(defn- drain-reader
|
(defn- drain-reader
|
||||||
|
|
|
||||||
|
|
@ -3565,4 +3565,13 @@
|
||||||
{: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 "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 / 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] +)]"}
|
{: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] +)]"}
|
||||||
|
{:suite "edn / strictness" :label "duplicate literal keys and elements throw" :expected "[:dup :dup]" :actual "[(try (clojure.edn/read-string \"{:a 1 :a 2}\") (catch IllegalArgumentException _ :dup)) (try (clojure.edn/read-string \"#{:a :a}\") (catch IllegalArgumentException _ :dup))]"}
|
||||||
|
{:suite "edn / strictness" :label "invalid numbers and ratio edges throw" :expected "[:ae :nfe :nfe :nfe :nfe]" :actual "[(try (clojure.edn/read-string \"1/0\") (catch ArithmeticException _ :ae)) (try (clojure.edn/read-string \"1/-1\") (catch NumberFormatException _ :nfe)) (try (clojure.edn/read-string \"08\") (catch NumberFormatException _ :nfe)) (try (clojure.edn/read-string \"1a\") (catch NumberFormatException _ :nfe)) (try (clojure.edn/read-string \"0x2g\") (catch NumberFormatException _ :nfe))]"}
|
||||||
|
{:suite "edn / strictness" :label "invalid tokens throw; :/ and ns// are valid" :expected "[:t :t :t :t \"/\" \"/\"]" :actual "[(try (clojure.edn/read-string \"::foo\") (catch Throwable _ :t)) (try (clojure.edn/read-string \":\") (catch Throwable _ :t)) (try (clojure.edn/read-string \"foo/\") (catch Throwable _ :t)) (try (clojure.edn/read-string \"/foo\") (catch Throwable _ :t)) (name (clojure.edn/read-string \":/\")) (name (clojure.edn/read-string \"foo//\"))]"}
|
||||||
|
{:suite "edn / strictness" :label "unmatched delimiters, bad escapes, octal range throw" :expected "[:t :t :t :t]" :actual "[(try (clojure.edn/read-string \")\") (catch Throwable _ :t)) (try (clojure.edn/read-string \"]\") (catch Throwable _ :t)) (try (clojure.edn/read-string \"\\\"\\\\q\\\"\") (catch Throwable _ :t)) (try (clojure.edn/read-string \"\\\\o400\") (catch Throwable _ :t))]"}
|
||||||
|
{:suite "edn / strictness" :label "eof honors :eof; a missing :eof is an error; \\r ends comments" :expected "[:END :t 3]" :actual "[(clojure.edn/read-string {:eof :END} \"\") (try (clojure.edn/read-string {} \" \") (catch Throwable _ :t)) (clojure.edn/read-string \";c\\r3\\n5\")]"}
|
||||||
|
{:suite "edn / strictness" :label "M literals construct; lists are lists; discarded tags validate" :expected "[-3.14M true :t]" :actual "[(clojure.edn/read-string \"-3.14M\") (list? (clojure.edn/read-string \"(1 (2))\")) (try (clojure.edn/read-string \"#_ #foo 0\") (catch Throwable _ :t))]"}
|
||||||
|
{:suite "edn / strictness" :label "#inst and #uuid validate their fields" :expected "[:t :t :t]" :actual "[(try (clojure.edn/read-string \"#inst \\\"2010-02-29T00:00:00.000Z\\\"\") (catch Throwable _ :t)) (try (clojure.edn/read-string \"#inst \\\"2010-01-01T24:00:00.000Z\\\"\") (catch Throwable _ :t)) (try (clojure.edn/read-string \"#uuid \\\"not-a-uuid\\\"\") (catch Throwable _ :t))]"}
|
||||||
|
{:suite "reader / strict tokens" :label "the core reader rejects invalid numbers and duplicate keys too" :expected "[:nfe :dup 34]" :actual "[(try (read-string \"1a\") (catch NumberFormatException _ :nfe)) (try (read-string \"{:a 1 :a 2}\") (catch IllegalArgumentException _ :dup)) (read-string \"042\")]"}
|
||||||
|
{:suite "reader / symbol intern" :label "1-arg symbol splits ns at the first slash (Symbol.intern)" :expected "[\"foo\" \"bar/baz\" \"/\"]" :actual "[(namespace (symbol \"foo/bar/baz\")) (name (symbol \"foo/bar/baz\")) (name (symbol \"foo//\"))]"}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -57,4 +57,3 @@ clojure.core-test.update 1 0
|
||||||
clojure.core-test.vals 0 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
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue