data.csv: fully passes, three general fixes
clojure.data.csv runs its whole suite on jolt (4/4 reading/writing/eof/line- endings). Three general gaps fixed, all runtime, no re-mint, JVM-certified: - The prefix-list form of :require/:use — (:require (clojure [string :as str])) means clojure.string :as str — now expands (loader.ss). It silently failed before, trying to load a "clojure" namespace. - extend-protocol to java.io.Reader / Writer / StringReader / PushbackReader now dispatches: those reader/writer host tags carry the right class names in value-host-tags AND are in host-type-set, so extend-protocol registers under the canonical tag instead of a local ns tag (records.ss). data.csv's Read-CSV-From protocol extends to String / Reader / PushbackReader. - (str StringWriter) returns its accumulated content (register-str-render for the "writer" jhost), not the opaque host object — data.csv writes CSV to one and reads it back. Listed in docs/libraries.md + the site. make test green (+2 corpus rows, 0 new divergences), shakesmoke byte-identical.
This commit is contained in:
parent
745d22260f
commit
44837f01ab
5 changed files with 74 additions and 23 deletions
|
|
@ -60,6 +60,7 @@ e.g. the [ring-app example](https://github.com/jolt-lang/examples/tree/main/ring
|
||||||
* [data.zip](https://github.com/clojure/data.zip) — zipper navigation, including
|
* [data.zip](https://github.com/clojure/data.zip) — zipper navigation, including
|
||||||
`clojure.data.zip.xml`; XML parsing via [jolt-lang/xml](https://github.com/jolt-lang/xml)
|
`clojure.data.zip.xml`; XML parsing via [jolt-lang/xml](https://github.com/jolt-lang/xml)
|
||||||
(which now ships `clojure.xml/parse`).
|
(which now ships `clojure.xml/parse`).
|
||||||
|
* [data.csv](https://github.com/clojure/data.csv) — reading and writing CSV.
|
||||||
* [tick](https://github.com/juxt/tick) — date/time over Jolt's `java.time`;
|
* [tick](https://github.com/juxt/tick) — date/time over Jolt's `java.time`;
|
||||||
`#time/…` literals via `time-literals`.
|
`#time/…` literals via `time-literals`.
|
||||||
* [transit-jolt](https://github.com/jolt-lang/transit-jolt) — Transit (JSON) read/write
|
* [transit-jolt](https://github.com/jolt-lang/transit-jolt) — Transit (JSON) read/write
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,9 @@
|
||||||
(cons "flush" (lambda (self) jolt-nil))
|
(cons "flush" (lambda (self) jolt-nil))
|
||||||
(cons "close" (lambda (self) jolt-nil))
|
(cons "close" (lambda (self) jolt-nil))
|
||||||
(cons "toString" (lambda (self) (sb-str self)))))
|
(cons "toString" (lambda (self) (sb-str self)))))
|
||||||
|
;; (str sw) / print a StringWriter -> its accumulated content, like the JVM
|
||||||
|
;; (str calls toString) — data.csv writes CSV to a StringWriter and reads it back.
|
||||||
|
(register-str-render! (lambda (x) (and (jhost? x) (string=? (jhost-tag x) "writer"))) sb-str)
|
||||||
|
|
||||||
;; a file-backed writer (clojure.java.io/writer of a File/path): accumulates like
|
;; a file-backed writer (clojure.java.io/writer of a File/path): accumulates like
|
||||||
;; StringWriter, then persists to the path on flush/close, so
|
;; StringWriter, then persists to the path on flush/close, so
|
||||||
|
|
|
||||||
|
|
@ -227,39 +227,67 @@
|
||||||
(else '()))))
|
(else '()))))
|
||||||
(and (pair? items) (symbol-t? (car items)) (symbol-t-name (car items)))))
|
(and (pair? items) (symbol-t? (car items)) (symbol-t-name (car items)))))
|
||||||
|
|
||||||
|
;; A libspec under a prefix joins onto it: a bare symbol `string` -> `prefix.string`,
|
||||||
|
;; a vector `[string :as s]` -> `[prefix.string :as s]` (opts preserved).
|
||||||
|
(define (prefix-join prefix lib)
|
||||||
|
(cond
|
||||||
|
((symbol-t? lib) (jolt-symbol #f (string-append prefix "." (symbol-t-name lib))))
|
||||||
|
((pvec? lib)
|
||||||
|
(let ((items (seq->list lib)))
|
||||||
|
(if (and (pair? items) (symbol-t? (car items)))
|
||||||
|
(apply jolt-vector (jolt-symbol #f (string-append prefix "." (symbol-t-name (car items)))) (cdr items))
|
||||||
|
lib)))
|
||||||
|
(else lib)))
|
||||||
|
|
||||||
|
;; The prefix-list form of a require/use spec: a LIST `(prefix lib …)` expands to
|
||||||
|
;; one spec per lib (prefix.lib), so (:require (clojure [string :as str])) means
|
||||||
|
;; clojure.string :as str. A vector / symbol spec is already a single lib.
|
||||||
|
(define (expand-spec s)
|
||||||
|
(if (or (cseq? s) (empty-list-t? s))
|
||||||
|
(let ((items (seq->list s)))
|
||||||
|
(if (and (pair? items) (symbol-t? (car items)) (pair? (cdr items)))
|
||||||
|
(map (lambda (lib) (prefix-join (symbol-t-name (car items)) lib)) (cdr items))
|
||||||
|
(list s)))
|
||||||
|
(list s)))
|
||||||
|
|
||||||
;; --- require/use that LOAD ---------------------------------------------------
|
;; --- require/use that LOAD ---------------------------------------------------
|
||||||
;; Override the alias-only versions from natives-str.ss. Load each spec's target
|
;; Override the alias-only versions from natives-str.ss. Load each spec's target
|
||||||
;; (no-op if baked/already loaded), THEN register its :as/:refer under the caller
|
;; (no-op if baked/already loaded), THEN register its :as/:refer under the caller
|
||||||
;; ns (chez-register-spec! reads the current ns, restored by load-namespace).
|
;; ns (chez-register-spec! reads the current ns, restored by load-namespace).
|
||||||
(define (loader-require . specs)
|
(define (loader-require . specs)
|
||||||
(for-each
|
(for-each
|
||||||
(lambda (s)
|
(lambda (s0)
|
||||||
(let ((target (spec-target-name s)))
|
(for-each
|
||||||
(when target (load-namespace target)))
|
(lambda (s)
|
||||||
(chez-register-spec! (chez-current-ns) s))
|
(let ((target (spec-target-name s)))
|
||||||
|
(when target (load-namespace target)))
|
||||||
|
(chez-register-spec! (chez-current-ns) s))
|
||||||
|
(expand-spec s0)))
|
||||||
specs)
|
specs)
|
||||||
jolt-nil)
|
jolt-nil)
|
||||||
(def-var! "clojure.core" "require" loader-require)
|
(def-var! "clojure.core" "require" loader-require)
|
||||||
|
|
||||||
(define (loader-use . specs)
|
(define (loader-use . specs0)
|
||||||
(for-each
|
(for-each
|
||||||
(lambda (spec)
|
(lambda (spec0)
|
||||||
(let ((target (spec-target-name spec)))
|
(for-each
|
||||||
(when target (load-namespace target)))
|
(lambda (spec)
|
||||||
(chez-register-spec! (chez-current-ns) spec)
|
(let ((target (spec-target-name spec)))
|
||||||
(let* ((items (cond ((pvec? spec) (seq->list spec))
|
(when target (load-namespace target)))
|
||||||
((or (cseq? spec) (empty-list-t? spec)) (seq->list spec))
|
(chez-register-spec! (chez-current-ns) spec)
|
||||||
((symbol-t? spec) (list spec))
|
(let* ((items (cond ((pvec? spec) (seq->list spec))
|
||||||
(else '())))
|
((symbol-t? spec) (list spec))
|
||||||
(target (and (pair? items) (symbol-t? (car items)) (symbol-t-name (car items))))
|
(else '())))
|
||||||
(filtered (let scan ((xs (if (pair? items) (cdr items) '())))
|
(target (and (pair? items) (symbol-t? (car items)) (symbol-t-name (car items))))
|
||||||
(cond ((null? xs) #f)
|
(filtered (let scan ((xs (if (pair? items) (cdr items) '())))
|
||||||
((and (keyword? (car xs))
|
(cond ((null? xs) #f)
|
||||||
(member (keyword-t-name (car xs)) '("only" "refer"))) #t)
|
((and (keyword? (car xs))
|
||||||
(else (scan (cdr xs)))))))
|
(member (keyword-t-name (car xs)) '("only" "refer"))) #t)
|
||||||
(when (and target (not filtered))
|
(else (scan (cdr xs)))))))
|
||||||
(chez-register-refer-all! (chez-current-ns) target))))
|
(when (and target (not filtered))
|
||||||
specs)
|
(chez-register-refer-all! (chez-current-ns) target))))
|
||||||
|
(expand-spec spec0)))
|
||||||
|
specs0)
|
||||||
jolt-nil)
|
jolt-nil)
|
||||||
(def-var! "clojure.core" "use" loader-use)
|
(def-var! "clojure.core" "use" loader-use)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -469,6 +469,18 @@
|
||||||
((and (jhost? obj) (string=? (jhost-tag obj) "uri")) '("URI" "java.net.URI" "Object"))
|
((and (jhost? obj) (string=? (jhost-tag obj) "uri")) '("URI" "java.net.URI" "Object"))
|
||||||
;; a ByteBuffer — extend-protocol java.nio.ByteBuffer (aws-api util).
|
;; a ByteBuffer — extend-protocol java.nio.ByteBuffer (aws-api util).
|
||||||
((and (jhost? obj) (string=? (jhost-tag obj) "byte-buffer")) '("ByteBuffer" "java.nio.ByteBuffer" "Object"))
|
((and (jhost? obj) (string=? (jhost-tag obj) "byte-buffer")) '("ByteBuffer" "java.nio.ByteBuffer" "Object"))
|
||||||
|
;; java.io readers/writers — so (extend-protocol java.io.Reader …) (data.csv)
|
||||||
|
;; and the like dispatch on one. A PushbackReader is also a Reader.
|
||||||
|
((and (jhost? obj) (string=? (jhost-tag obj) "string-reader"))
|
||||||
|
'("StringReader" "java.io.StringReader" "Reader" "java.io.Reader" "Object"))
|
||||||
|
((and (jhost? obj) (string=? (jhost-tag obj) "pushback-reader"))
|
||||||
|
'("PushbackReader" "java.io.PushbackReader" "FilterReader" "java.io.FilterReader" "Reader" "java.io.Reader" "Object"))
|
||||||
|
((and (jhost? obj) (string=? (jhost-tag obj) "char-reader"))
|
||||||
|
'("Reader" "java.io.Reader" "Object"))
|
||||||
|
((and (jhost? obj) (string=? (jhost-tag obj) "char-writer"))
|
||||||
|
'("Writer" "java.io.Writer" "Object"))
|
||||||
|
((and (jhost? obj) (string=? (jhost-tag obj) "writer"))
|
||||||
|
'("Writer" "java.io.Writer" "Object"))
|
||||||
;; arrays dispatch by their JVM array-class name — extend-protocol to
|
;; arrays dispatch by their JVM array-class name — extend-protocol to
|
||||||
;; (Class/forName "[B") for byte[] (data.json, aws-api), "[C" for char[].
|
;; (Class/forName "[B") for byte[] (data.json, aws-api), "[C" for char[].
|
||||||
((and (jolt-array? obj) (eq? (jolt-array-kind obj) 'byte)) '("[B" "Object"))
|
((and (jolt-array? obj) (eq? (jolt-array-kind obj) 'byte)) '("[B" "Object"))
|
||||||
|
|
@ -606,7 +618,12 @@
|
||||||
"ChronoUnit" "ChronoField" "TemporalAmount" "TemporalUnit" "TemporalField"
|
"ChronoUnit" "ChronoField" "TemporalAmount" "TemporalUnit" "TemporalField"
|
||||||
;; ByteBuffer + JVM array classes (extend-protocol to (Class/forName "[B"))
|
;; ByteBuffer + JVM array classes (extend-protocol to (Class/forName "[B"))
|
||||||
"ByteBuffer" "java.nio.ByteBuffer"
|
"ByteBuffer" "java.nio.ByteBuffer"
|
||||||
"[B" "[C" "[I" "[J" "[D" "[Ljava.lang.Object;"))
|
"[B" "[C" "[I" "[J" "[D" "[Ljava.lang.Object;"
|
||||||
|
;; java.io readers/writers — extend-protocol java.io.Reader (data.csv)
|
||||||
|
"Reader" "java.io.Reader" "Writer" "java.io.Writer"
|
||||||
|
"StringReader" "java.io.StringReader" "PushbackReader" "java.io.PushbackReader"
|
||||||
|
"BufferedReader" "java.io.BufferedReader" "FilterReader" "java.io.FilterReader"
|
||||||
|
"InputStream" "java.io.InputStream" "OutputStream" "java.io.OutputStream"))
|
||||||
h))
|
h))
|
||||||
(define (strip-prefix s p)
|
(define (strip-prefix s p)
|
||||||
(let ((pl (string-length p)))
|
(let ((pl (string-length p)))
|
||||||
|
|
|
||||||
|
|
@ -3367,4 +3367,6 @@
|
||||||
{:suite "host-interop / instance? hierarchy" :label "ExceptionInfo is a RuntimeException" :expected "[true true true]" :actual "[(instance? RuntimeException (ex-info \"x\" {})) (instance? Exception (ex-info \"x\" {})) (instance? Throwable (ex-info \"x\" {}))]"}
|
{:suite "host-interop / instance? hierarchy" :label "ExceptionInfo is a RuntimeException" :expected "[true true true]" :actual "[(instance? RuntimeException (ex-info \"x\" {})) (instance? Exception (ex-info \"x\" {})) (instance? Throwable (ex-info \"x\" {}))]"}
|
||||||
{:suite "host-interop / class hierarchy" :label "Symbol and Keyword are IFn" :expected "[true true]" :actual "[(isa? clojure.lang.Symbol clojure.lang.IFn) (isa? clojure.lang.Keyword clojure.lang.IFn)]"}
|
{:suite "host-interop / class hierarchy" :label "Symbol and Keyword are IFn" :expected "[true true]" :actual "[(isa? clojure.lang.Symbol clojure.lang.IFn) (isa? clojure.lang.Keyword clojure.lang.IFn)]"}
|
||||||
{:suite "host-interop / class hierarchy" :label "(class sym)-dispatched multimethod hits an IFn method" :expected ":ifn" :actual "(do (defmulti cm1 class) (defmethod cm1 clojure.lang.IFn [_] :ifn) (cm1 (quote sym)))"}
|
{:suite "host-interop / class hierarchy" :label "(class sym)-dispatched multimethod hits an IFn method" :expected ":ifn" :actual "(do (defmulti cm1 class) (defmethod cm1 clojure.lang.IFn [_] :ifn) (cm1 (quote sym)))"}
|
||||||
|
{:suite "host-interop / extend-protocol java.io" :label "protocol extended to Reader / String dispatches a StringReader and a String" :expected "[:reader :string]" :actual "(do (import (quote (java.io StringReader))) (defprotocol Prdr (mrd [x])) (extend-protocol Prdr java.io.Reader (mrd [_] :reader) String (mrd [_] :string)) [(mrd (StringReader. \"x\")) (mrd \"y\")])"}
|
||||||
|
{:suite "host-interop / StringWriter" :label "(str StringWriter) returns its accumulated content" :expected "\"hi!\"" :actual "(do (import (quote (java.io StringWriter))) (let [w (StringWriter.)] (.write w \"hi\") (.write w \"!\") (str w)))"}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue