Merge pull request #189 from jolt-lang/spike/auto-resolved-keywords

Auto-resolved keywords, edn reader opts, and related reader/runtime fixes
This commit is contained in:
Dmitri Sotnikov 2026-06-24 16:07:52 +00:00 committed by GitHub
commit 5b55c7f7b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 948 additions and 748 deletions

View file

@ -59,8 +59,9 @@ keyword := ':' name | ':' ns '/' name | '::' name | '::' alias '/' name
. $ & %` (with `%` and `&` further constrained inside `#()`); a symbol . $ & %` (with `%` and `&` further constrained inside `#()`); a symbol
MUST NOT begin with a digit; `.` and `/` have positional restrictions. MUST NOT begin with a digit; `.` and `/` have positional restrictions.
- S7. `::kw` MUST resolve to the current namespace at *read* time - S7. `::kw` MUST resolve to the current namespace at *read* time
(`::k` in ns `user` reads as `:user/k`); `::alias/k` resolves the alias or (`::k` in ns `user` reads as `:user/k`); `::alias/k` resolves `alias` through
MUST be a read error if the alias does not exist. the current namespace's aliases. (Clojure raises a read error for an unknown
alias; jolt reads it as `:alias/k`.)
### Strings and characters ### Strings and characters

View file

@ -46,7 +46,8 @@ HELLO FROM A BUILT BINARY!
args: [alpha bb ccc] args: [alpha bb ccc]
sum: 10 sum: 10
greet-default: greet:default greet-default: greet:default
greet-loud: greet:loud' greet-loud: greet:loud
greet-soft: greet:soft'
if [ "$got" != "$want" ]; then if [ "$got" != "$want" ]; then
echo " FAIL: binary output mismatch" echo " FAIL: binary output mismatch"
echo "--- want ---"; echo "$want" echo "--- want ---"; echo "$want"

View file

@ -185,10 +185,26 @@
(let loop ((xs (cdr items))) (let loop ((xs (cdr items)))
(when (and (pair? xs) (pair? (cdr xs))) (when (and (pair? xs) (pair? (cdr xs)))
(let ((k (car xs)) (v (cadr xs))) (let ((k (car xs)) (v (cadr xs)))
(when (and (keyword? k) (string=? (keyword-t-name k) "as") (symbol-t? v)) (when (keyword? k)
(cond
((and (string=? (keyword-t-name k) "as") (symbol-t? v))
(emit! (string-append "(chez-register-alias! " (ei-str-lit ns-name) (emit! (string-append "(chez-register-alias! " (ei-str-lit ns-name)
" " (ei-str-lit (symbol-t-name v)) " " (ei-str-lit (symbol-t-name v))
" " (ei-str-lit target) ")")))
;; :refer [a b] / :refer :all — a defmethod on a referred multifn
;; resolves the bare name through the refer table at runtime.
((or (string=? (keyword-t-name k) "refer") (string=? (keyword-t-name k) "only"))
(cond
((and (keyword? v) (string=? (keyword-t-name v) "all"))
(emit! (string-append "(chez-register-refer-all! " (ei-str-lit ns-name)
" " (ei-str-lit target) ")")))
((or (pvec? v) (and (cseq? v) (cseq-list? v)))
(for-each (lambda (n)
(when (symbol-t? n)
(emit! (string-append "(chez-register-refer! " (ei-str-lit ns-name)
" " (ei-str-lit (symbol-t-name n))
" " (ei-str-lit target) ")")))) " " (ei-str-lit target) ")"))))
(seq->list v))))))))
(loop (cddr xs)))))))) (loop (cddr xs))))))))
(define (bld-ns-prelude ns-name src) (define (bld-ns-prelude ns-name src)

View file

@ -25,3 +25,6 @@
;; *print-readably* — bound by pr-family / with-out-str-style code; default true. ;; *print-readably* — bound by pr-family / with-out-str-style code; default true.
(def-var! "clojure.core" "*print-readably*" #t) (def-var! "clojure.core" "*print-readably*" #t)
;; *print-meta* — when true, pr prints metadata with a ^ prefix; default false.
(def-var! "clojure.core" "*print-meta*" #f)

View file

@ -59,6 +59,10 @@
;; that fails to emit) and `jolt build` (bld-emit-ns: optimize? #t, guard? #f — ;; that fails to emit) and `jolt build` (bld-emit-ns: optimize? #t, guard? #f —
;; strict, a failing form errors the build). ;; strict, a failing form errors the build).
(define (ei-emit-ns* ns-name src optimize? guard?) (define (ei-emit-ns* ns-name src optimize? guard?)
;; set the ns before reading so ::kw auto-resolves against this ns (the runtime
;; loader reads form-by-form after the ns form sets it; the cross-compile reads
;; all forms up front, so set it here).
(set-chez-ns! ns-name)
(let loop ((forms (ei-read-all src)) (acc '())) (let loop ((forms (ei-read-all src)) (acc '()))
(if (null? forms) (if (null? forms)
(reverse acc) (reverse acc)
@ -92,6 +96,7 @@
;; record whose refs are roots. A macro is prunable — its expander isn't called at ;; record whose refs are roots. A macro is prunable — its expander isn't called at
;; runtime in an AOT build. ;; runtime in an AOT build.
(define (ei-emit-ns-records ns-name src) (define (ei-emit-ns-records ns-name src)
(set-chez-ns! ns-name) ; ::kw resolves against this ns (see ei-emit-ns*)
(let loop ((forms (ei-read-all src)) (acc '())) (let loop ((forms (ei-read-all src)) (acc '()))
(if (null? forms) (if (null? forms)
(reverse acc) (reverse acc)

View file

@ -86,6 +86,10 @@
(define (hc-sym-meta x) (define (hc-sym-meta x)
(let ((m (symbol-t-meta x))) (let ((m (symbol-t-meta x)))
(if (and m (not (jolt-nil? m)) (not (null? m))) m jolt-nil))) (if (and m (not (jolt-nil? m)) (not (null? m))) m jolt-nil)))
;; Metadata the reader attached to a collection literal (vec/map/set/list), or
;; jolt-nil. The analyzer re-emits a runtime (with-meta ..) for a meta-carrying
;; vector/map/set so the value keeps its metadata.
(define (hc-coll-meta x) (jolt-meta x))
;; list items -> jolt vector (pvec); the analyzer mapv's over the result. ;; list items -> jolt vector (pvec); the analyzer mapv's over the result.
(define (hc-elements x) (define (hc-elements x)
@ -327,6 +331,7 @@
(def-var! "jolt.host" "form-sym-name" hc-sym-name) (def-var! "jolt.host" "form-sym-name" hc-sym-name)
(def-var! "jolt.host" "form-sym-ns" hc-sym-ns) (def-var! "jolt.host" "form-sym-ns" hc-sym-ns)
(def-var! "jolt.host" "form-sym-meta" hc-sym-meta) (def-var! "jolt.host" "form-sym-meta" hc-sym-meta)
(def-var! "jolt.host" "form-coll-meta" hc-coll-meta)
(def-var! "jolt.host" "form-list?" hc-list?) (def-var! "jolt.host" "form-list?" hc-list?)
(def-var! "jolt.host" "form-vec?" hc-vec?) (def-var! "jolt.host" "form-vec?" hc-vec?)
(def-var! "jolt.host" "form-map?" hc-map?) (def-var! "jolt.host" "form-map?" hc-map?)

View file

@ -218,6 +218,12 @@
;; state: a vector #(wrapped-reader pushed-list) ;; state: a vector #(wrapped-reader pushed-list)
(register-class-ctor! "PushbackReader" (register-class-ctor! "PushbackReader"
(lambda (rdr . _) (make-jhost "pushback-reader" (vector rdr '())))) (lambda (rdr . _) (make-jhost "pushback-reader" (vector rdr '()))))
;; LineNumberingPushbackReader: a pushback-reader (jolt doesn't track line
;; numbers; getLineNumber is a stub for error-reporting paths that read it).
(register-class-ctor! "LineNumberingPushbackReader"
(lambda (rdr . _) (make-jhost "pushback-reader" (vector rdr '()))))
(register-class-ctor! "clojure.lang.LineNumberingPushbackReader"
(lambda (rdr . _) (make-jhost "pushback-reader" (vector rdr '()))))
(define (read-unit r) ; read one code unit (flonum) from any reader, -1 at EOF (define (read-unit r) ; read one code unit (flonum) from any reader, -1 at EOF
(record-method-dispatch r "read" jolt-nil)) (record-method-dispatch r "read" jolt-nil))
(register-host-methods! "pushback-reader" (register-host-methods! "pushback-reader"
@ -230,7 +236,8 @@
(vector-set! (jhost-state self) 1 (vector-set! (jhost-state self) 1
(cons (if (char? ch) (->num (char->integer ch)) ch) (vector-ref (jhost-state self) 1))) (cons (if (char? ch) (->num (char->integer ch)) ch) (vector-ref (jhost-state self) 1)))
jolt-nil)) jolt-nil))
(cons "close" (lambda (self) jolt-nil)))) (cons "close" (lambda (self) jolt-nil))
(cons "getLineNumber" (lambda (self) 0))))
;; ---- StringTokenizer -------------------------------------------------------- ;; ---- StringTokenizer --------------------------------------------------------
;; state: a vector #(tokens-list pos) ;; state: a vector #(tokens-list pos)
@ -499,5 +506,37 @@
(def-var! "clojure.core" "__register-instance-check!" (def-var! "clojure.core" "__register-instance-check!"
(lambda (f) (set! user-instance-checks (append user-instance-checks (list f))) jolt-nil)) (lambda (f) (set! user-instance-checks (append user-instance-checks (list f))) jolt-nil))
;; (instance? clojure.lang.IFoo x) for the core clojure.lang interfaces libraries
;; branch on — jolt's value model satisfies them, so report it. Matched by the
;; interface's last dotted segment, so "clojure.lang.IObj" and "IObj" both hit.
(define (hsc-last-segment 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))))))
;; values that carry metadata (mirrors jolt-with-meta's set in natives-meta.ss).
(define (hsc-imeta? x)
(or (pvec? x) (pmap? x) (pset? x) (cseq? x) (empty-list-t? x)
(jolt-lazyseq? x) (jrec? x) (procedure? x) (symbol-t? x)))
(register-instance-check-arm!
(lambda (type-sym val)
(let ((iface (hsc-last-segment (symbol-t-name type-sym))))
(let ((hit (cond
((or (string=? iface "IObj") (string=? iface "IMeta")) (hsc-imeta? val))
((or (string=? iface "IMapEntry") (string=? iface "MapEntry")) (jolt-map-entry? val))
((string=? iface "IRecord") (jrec? val))
((string=? iface "IPersistentMap") (or (pmap? val) (htable-sorted-map? val)))
((string=? iface "IPersistentVector") (and (pvec? val) (not (jolt-map-entry? val))))
((string=? iface "IPersistentSet") (or (pset? val) (htable-sorted-set? val)))
((or (string=? iface "ISeq") (string=? iface "Seqable"))
(or (cseq? val) (empty-list-t? val) (jolt-lazyseq? val)))
((string=? iface "Sequential")
(or (pvec? val) (cseq? val) (empty-list-t? val) (jolt-lazyseq? val)))
((string=? iface "IFn")
(or (procedure? val) (keyword? val) (symbol-t? val)
(pmap? val) (pset? val) (pvec? val)))
(else 'none))))
(if (eq? hit 'none) 'pass (if hit #t #f))))))
;; (jolt.host/table? x) — is x a host tagged-table? ;; (jolt.host/table? x) — is x a host tagged-table?
(def-var! "jolt.host" "table?" (lambda (x) (if (htable? x) #t #f))) (def-var! "jolt.host" "table?" (lambda (x) (if (htable? x) #t #f)))

View file

@ -388,3 +388,11 @@
((string=? method-name "after") (> (jinst-ms obj) (ms-of (car (seq->list rest-args))))) ((string=? method-name "after") (> (jinst-ms obj) (ms-of (car (seq->list rest-args)))))
(else (error #f (string-append "No method " method-name " on Date"))))) (else (error #f (string-append "No method " method-name " on Date")))))
(else (%it-rmd obj method-name rest-args))))) (else (%it-rmd obj method-name rest-args)))))
;; Clojure's built-in data readers, so a library that merges default-data-readers
;; or binds *data-readers* (e.g. aero's reader opts) resolves #inst / #uuid.
;; Keyed by symbol, like Clojure. *data-readers* is the bindable user table.
(def-var! "clojure.core" "default-data-readers"
(jolt-hash-map (jolt-symbol #f "inst") jolt-inst-from-string
(jolt-symbol #f "uuid") jolt-uuid-from-string))
(def-var! "clojure.core" "*data-readers*" empty-pmap)

View file

@ -100,6 +100,7 @@
((string=? name "exists") (list (if (file-exists? p) #t #f))) ((string=? name "exists") (list (if (file-exists? p) #t #f)))
((string=? name "isDirectory") (list (if (file-directory? p) #t #f))) ((string=? name "isDirectory") (list (if (file-directory? p) #t #f)))
((string=? name "isFile") (list (if (and (file-exists? p) (not (file-directory? p))) #t #f))) ((string=? name "isFile") (list (if (and (file-exists? p) (not (file-directory? p))) #t #f)))
((string=? name "isAbsolute") (list (if (and (> (string-length p) 0) (char=? (string-ref p 0) #\/)) #t #f)))
((string=? name "listFiles") (list (list->cseq (map make-jfile (jolt-list-dir p))))) ((string=? name "listFiles") (list (list->cseq (map make-jfile (jolt-list-dir p)))))
((string=? name "getParent") ((string=? name "getParent")
(let loop ((i (- (string-length p) 1))) (let loop ((i (- (string-length p) 1)))
@ -211,7 +212,7 @@
;; a byte input-stream shim (e.g. clj-http-lite's :as :stream body): drain it. ;; a byte input-stream shim (e.g. clj-http-lite's :as :stream body): drain it.
((and (htable? src) (jolt-truthy? (jolt-ref-get src (keyword "jolt" "input-stream")))) ((and (htable? src) (jolt-truthy? (jolt-ref-get src (keyword "jolt" "input-stream"))))
(decode-bytevector (drain-byte-stream src) (slurp-encoding opts))) (decode-bytevector (drain-byte-stream src) (slurp-encoding opts)))
((string? src) (read-file-string src)) ((string? src) (read-file-string (project-relative src)))
(else (error #f "slurp: unsupported source" src)))) (else (error #f "slurp: unsupported source" src))))
(define (spit-append? opts) (define (spit-append? opts)
@ -300,7 +301,7 @@
((embedded-res? x) (host-new "StringReader" (embedded-res-content x))) ((embedded-res? x) (host-new "StringReader" (embedded-res-content x)))
((and (jhost? x) (string=? (jhost-tag x) "url")) ((and (jhost? x) (string=? (jhost-tag x) "url"))
(host-new "StringReader" (read-file-string (url-strip-scheme (url-spec x))))) (host-new "StringReader" (read-file-string (url-strip-scheme (url-spec x)))))
((string? x) (host-new "StringReader" (read-file-string x))) ((string? x) (host-new "StringReader" (read-file-string (project-relative x))))
((or (cseq? x) (empty-list-t? x) (pvec? x)) ((or (cseq? x) (empty-list-t? x) (pvec? x))
(host-new "StringReader" (seq-source->string x))) (host-new "StringReader" (seq-source->string x)))
(else (host-new "StringReader" (jolt-str-render-one x))))) (else (host-new "StringReader" (jolt-str-render-one x)))))

View file

@ -73,8 +73,14 @@
(sns (symbol-t-ns mm-sym)) (sns (symbol-t-ns mm-sym))
(qns (and sns (not (jolt-nil? sns)) (not (null? sns)) sns)) (qns (and sns (not (jolt-nil? sns)) (not (null? sns)) sns))
;; qualified (cf.mm/ext) resolves in its own ns (cross-ns defmethod); ;; qualified (cf.mm/ext) resolves in its own ns (cross-ns defmethod);
;; unqualified stays in the current ns (a shadow, as before). ;; unqualified resolves in the current ns, else a :refer's home ns (so a
(mns (if qns (or (chez-resolve-alias (chez-current-ns) qns) qns) (chez-current-ns))) ;; defmethod on a referred multifn lands on the real one), else stays in
;; the current ns (a shadow, as before).
(mns (cond
(qns (or (chez-resolve-alias (chez-current-ns) qns) qns))
((var-cell-lookup (chez-current-ns) nm) (chez-current-ns))
((chez-resolve-refer (chez-current-ns) nm) => values)
(else (chez-current-ns))))
(cur (var-deref mns nm)) (cur (var-deref mns nm))
(mf (if (jolt-multifn? cur) cur (mf (if (jolt-multifn? cur) cur
;; auto-create: copy the dispatch fn + default from a same-named ;; auto-create: copy the dispatch fn + default from a same-named

View file

@ -19,7 +19,7 @@
(jolt-assoc (if user user (jolt-hash-map)) (jolt-assoc (if user user (jolt-hash-map))
jolt-kw-var-ns (var-cell-ns x) jolt-kw-var-ns (var-cell-ns x)
jolt-kw-var-name (var-cell-name x)))) jolt-kw-var-name (var-cell-name x))))
((or (pvec? x) (pmap? x) (pset? x) (cseq? x) (empty-list-t? x) (jrec? x) (procedure? x)) ((or (pvec? x) (pmap? x) (pset? x) (cseq? x) (empty-list-t? x) (jolt-lazyseq? x) (jrec? x) (procedure? x))
(hashtable-ref meta-table x jolt-nil)) (hashtable-ref meta-table x jolt-nil))
(else jolt-nil))) (else jolt-nil)))
@ -36,7 +36,7 @@
(define (jolt-with-meta x m) (define (jolt-with-meta x m)
(cond (cond
((symbol-t? x) (make-symbol-t (symbol-t-ns x) (symbol-t-name x) m)) ((symbol-t? x) (make-symbol-t (symbol-t-ns x) (symbol-t-name x) m))
((or (pvec? x) (pmap? x) (pset? x) (cseq? x) (empty-list-t? x) (jrec? x) (procedure? x)) ((or (pvec? x) (pmap? x) (pset? x) (cseq? x) (empty-list-t? x) (jolt-lazyseq? x) (jrec? x) (procedure? x))
(let ((c (meta-copy x))) (let ((c (meta-copy x)))
(if (jolt-nil? m) (hashtable-delete! meta-table c) (hashtable-set! meta-table c m)) (if (jolt-nil? m) (hashtable-delete! meta-table c) (hashtable-set! meta-table c m))
c)) c))

View file

@ -82,6 +82,13 @@
(else d)))) (else d))))
(define (jtagged-pr t) (string-append "#" (jolt-pr-str (jtagged-tag t)) " " (jolt-pr-readable (jtagged-form t)))) (define (jtagged-pr t) (string-append "#" (jolt-pr-str (jtagged-tag t)) " " (jolt-pr-readable (jtagged-form t))))
(register-pr-arm! jtagged? jtagged-pr) (register-pr-arm! jtagged? jtagged-pr)
;; two tagged literals are = iff same tag and (recursively) = form, like the JVM's
;; TaggedLiteral — so they work as map keys / set members. (jolt-hash already
;; hashes the fields structurally, so eq/hash stay consistent.)
(register-eq-arm! (lambda (a b) (or (jtagged? a) (jtagged? b)))
(lambda (a b) (and (jtagged? a) (jtagged? b)
(jolt=2 (jtagged-tag a) (jtagged-tag b))
(jolt=2 (jtagged-form a) (jtagged-form b)))))
(def-var! "clojure.core" "tagged-literal" jolt-tagged-literal) (def-var! "clojure.core" "tagged-literal" jolt-tagged-literal)
;; tagged-literal? is OVERLAY (reads :jolt/type) — asserted in post-prelude.ss. ;; tagged-literal? is OVERLAY (reads :jolt/type) — asserted in post-prelude.ss.

View file

@ -64,12 +64,14 @@
;; realized? reads :jolt/type and throws on a jolt-lazyseq record. ;; realized? reads :jolt/type and throws on a jolt-lazyseq record.
((jolt-lazyseq? x) (jolt-lazyseq-realized? x)) ((jolt-lazyseq? x) (jolt-lazyseq-realized? x))
(else (jolt-invoke overlay-realized? x)))))) (else (jolt-invoke overlay-realized? x))))))
;; clojure.edn/read over a reader: the overlay edn.clj drain-reader reads ;; clojure.edn/read over a reader: drain the jhost reader, then read through the
;; :jolt/type; the native version (io.ss) drains the jhost reader instead. ;; overlay read-string so the opts map (:readers/:default/:eof) is honored.
(def-var! "clojure.edn" "read" (def-var! "clojure.edn" "read"
(case-lambda (case-lambda
((reader) (chez-edn-read reader)) ((reader) (chez-edn-read reader))
((opts reader) (chez-edn-read reader)))) ((opts reader)
(jolt-invoke (var-deref "clojure.edn" "read-string") opts
(if (reader-jhost? reader) (drain-reader reader) (jolt-str-render-one reader))))))
;; line-seq: a jhost reader (io/reader result) -> drain+split; a map-reader (the ;; line-seq: a jhost reader (io/reader result) -> drain+split; a map-reader (the
;; overlay's :read-line-fn model, e.g. with-in-str) -> the overlay version. ;; overlay's :read-line-fn model, e.g. with-in-str) -> the overlay version.
(let ((overlay-line-seq (var-deref "clojure.core" "line-seq"))) (let ((overlay-line-seq (var-deref "clojure.core" "line-seq")))

View file

@ -67,12 +67,31 @@
(if (jolt-nil? s) (reverse acc) (if (jolt-nil? s) (reverse acc)
(loop (jolt-seq (seq-more s)) (cons (jolt-pr-readable (seq-first s)) acc))))) ")")) (loop (jolt-seq (seq-more s)) (cons (jolt-pr-readable (seq-first s)) acc))))) ")"))
(else (jolt-pr-str x)))) (else (jolt-pr-str x))))
(define (jolt-pr-readable x) (define (jolt-pr-readable-dispatch x)
(let loop ((as jolt-pr-readable-arms)) (let loop ((as jolt-pr-readable-arms))
(cond ((null? as) (jolt-pr-readable-base x)) (cond ((null? as) (jolt-pr-readable-base x))
(((caar as) x) ((cdar as) x)) (((caar as) x) ((cdar as) x))
(else (loop (cdr as)))))) (else (loop (cdr as))))))
;; *print-meta* support. The var is def'd after this file loads, so capture its
;; cell lazily; jolt-var-get (patched by dyn-binding.ss) honors a `binding`.
(define pr-meta-cell #f)
(define (pr-print-meta?)
(unless pr-meta-cell (set! pr-meta-cell (jolt-var "clojure.core" "*print-meta*")))
(jolt-truthy? (jolt-var-get pr-meta-cell)))
;; The metadata to print before x, or jolt-nil. A var prints as #'ns/name (its
;; {:ns :name} is derived, not user metadata) and a procedure is opaque — skip both.
(define (pr-user-meta x)
(if (or (var-cell? x) (procedure? x)) jolt-nil (jolt-meta x)))
(define (jolt-pr-readable x)
(if (pr-print-meta?)
(let ((m (pr-user-meta x)))
(if (jolt-nil? m)
(jolt-pr-readable-dispatch x)
(string-append "^" (jolt-pr-readable-dispatch m) " " (jolt-pr-readable-dispatch x))))
(jolt-pr-readable-dispatch x)))
;; __pr-str1: render ONE value readably (the overlay's pr-str joins these). ;; __pr-str1: render ONE value readably (the overlay's pr-str joins these).
(define (jolt-pr-str1 x) (jolt-pr-readable x)) (define (jolt-pr-str1 x) (jolt-pr-readable x))

View file

@ -290,20 +290,22 @@
((symbol-t? target) ((symbol-t? target)
(make-symbol-t (symbol-t-ns target) (symbol-t-name target) (make-symbol-t (symbol-t-ns target) (symbol-t-name target)
(rdr-merge-meta (symbol-t-meta target) meta))) (rdr-merge-meta (symbol-t-meta target) meta)))
;; A LIST form is code: ^Type (expr) is a compile-time hint on the FORM, not a
;; runtime operation. Attach the metadata to the form itself (so a quoted list
;; keeps it and the analyzer can read :tag) — never lower to a runtime
;; (with-meta (expr) meta), which would mis-apply the hint to the call's RESULT
;; and throw when that result is a string/number (e.g. ^String (to-str x)).
;; Matches Clojure: a tag hint on an evaluated form is discarded at runtime.
((cseq? target) (jolt-with-meta target meta))
((empty-list-t? target) target) ((empty-list-t? target) target)
;; A vector/map/set LITERAL: Clojure attaches the metadata to the runtime value ;; Lists/vectors/maps/sets attach metadata to the value itself, as Clojure's
;; ((meta ^{:tag :int} [1 2]) / ^:foo {}), so lower to a runtime (with-meta form ;; reader does. Reading DATA (read-string, edn) then preserves it. A list form
;; meta). Use the BARE `with-meta` symbol (ns #f) — the fn/defn macros unwrap a ;; is code: ^Type (expr) is a compile-time hint on the FORM, read off the form
;; (with-meta <arglist-vec> _) return-hint by matching the unqualified head, so a ;; for :tag and discarded at runtime (a hint on an evaluated form is dropped).
;; qualified clojure.core/with-meta would slip past them (^bytes [b]). ;; A vector/map/set LITERAL keeps it as a runtime value: the analyzer re-emits a
(else (jolt-list (jolt-symbol #f "with-meta") target meta)))) ;; (with-meta form meta) for a meta-carrying collection literal in code, so
;; (meta ^{:tag :int} [1 2]) / ^:foo {} still works.
(else
(let ((c (jolt-with-meta target meta)))
;; jolt-with-meta copies a pmap, giving it a fresh identity the rdr-map-order
;; side-table (source key order for left-to-right map-literal eval) loses —
;; carry the order entry over to the copy.
(let ((order (and (pmap? target) (hashtable-ref rdr-map-order target #f))))
(when order (hashtable-set! rdr-map-order c order)))
c))))
;; --- # dispatch ------------------------------------------------------------- ;; --- # dispatch -------------------------------------------------------------
;; #(...) anonymous fn shorthand: % -> p1, %N -> pN, %& -> rest. The ;; #(...) anonymous fn shorthand: % -> p1, %N -> pN, %& -> rest. The
@ -451,11 +453,19 @@
;; --- keyword ---------------------------------------------------------------- ;; --- keyword ----------------------------------------------------------------
(define (rdr-read-keyword s i end) ; i points just past the leading ':' (define (rdr-read-keyword s i end) ; i points just past the leading ':'
;; ::kw auto-resolves; drop the ns, so skip a second ':' ;; ::kw is auto-resolved against the current ns: ::name -> current-ns/name,
(let ((i (if (and (< i end) (char=? (string-ref s i) #\:)) (+ i 1) i))) ;; ::alias/name -> the alias's target ns / name (Clojure's reader semantics).
(let ((auto? (and (< i end) (char=? (string-ref s 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-values (((ns name) (rdr-sym-parts tok))) (let-values (((ns name) (rdr-sym-parts tok)))
(values (keyword ns name) j))))) (if auto?
(let* ((cur (chez-current-ns))
(rns (if (string? ns)
(let ((a (chez-resolve-alias cur ns))) (if a a ns))
cur)))
(values (keyword rns name) j))
(values (keyword ns name) j)))))))
;; --- the main dispatch ------------------------------------------------------ ;; --- the main dispatch ------------------------------------------------------
;; Returns (values form j). form is rdr-eof at end-of-input or at an unconsumed ;; Returns (values form j). form is rdr-eof at end-of-input or at an unconsumed

View file

@ -255,10 +255,21 @@
;; &message text plus any &irritants, or display-condition output as a fallback. ;; &message text plus any &irritants, or display-condition output as a fallback.
(define (condition->message-string c) (define (condition->message-string c)
(if (message-condition? c) (if (message-condition? c)
(let ((m (condition-message c)) (let* ((m (condition-message c))
(irr (if (irritants-condition? c) (condition-irritants c) '()))) (irr (if (irritants-condition? c) (condition-irritants c) '()))
(append-irr (lambda ()
(let loop ((xs irr) (acc m)) (let loop ((xs irr) (acc m))
(if (null? xs) acc (loop (cdr xs) (string-append acc " " (jolt-pr-str (car xs))))))) (if (null? xs) acc
(loop (cdr xs) (string-append acc " " (jolt-pr-str (car xs)))))))))
;; some Chez conditions (open-input-file etc.) carry a format-template
;; message ("failed for ~a: ~(~a~)") whose irritants fill the directives;
;; format it in. Fall back to appending the irritants if that fails.
(if (and (string? m) (let scan ((i 0))
(cond ((>= i (string-length m)) #f)
((char=? (string-ref m i) #\~) #t)
(else (scan (+ i 1))))))
(guard (e (#t (append-irr))) (apply format m irr))
(append-irr)))
(with-output-to-string (lambda () (display-condition c))))) (with-output-to-string (lambda () (display-condition c)))))
;; expose a Chez condition's message to Clojure (ex-message returns nil for raw ;; expose a Chez condition's message to Clojure (ex-message returns nil for raw
;; host conditions): the nREPL eval handler surfaces it instead of an opaque ;; host conditions): the nREPL eval handler surfaces it instead of an opaque

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
form-bigdec? form-bigdec-source form-bigdec? form-bigdec-source
form-ns-value? form-ns-value-name form-ns-value? form-ns-value-name
form-macro? form-expand-1 resolve-global form-macro? form-expand-1 resolve-global
form-sym-meta host-intern! form-syntax-quote-lower form-sym-meta form-coll-meta host-intern! form-syntax-quote-lower
record-type? record-ctor-key form-position late-bind? record-type? record-ctor-key form-position late-bind?
resolve-class-hint]])) resolve-class-hint]]))
@ -590,17 +590,31 @@
p (form-position form)] p (form-position form)]
(if p (assoc n :pos p) n))))))) (if p (assoc n :pos p) n)))))))
;; A vector/map/set literal carrying reader metadata (^:foo {…}, ^{:tag :int} [1])
;; keeps it as a runtime value: wrap the collection node in (with-meta coll meta).
;; The metadata is itself a form (its values may be expressions, ^{:a (f)}), so
;; analyze it. nil meta passes the node through. Arglist vectors never reach here —
;; analyze-arity reads their items directly — so a ^Type [args] hint is not wrapped.
(defn- with-coll-meta [ctx form env node]
(let [m (form-coll-meta form)]
(if (nil? m)
node
(invoke (var-ref "clojure.core" "with-meta") [node (analyze ctx m env)]))))
(defn analyze (defn analyze
([ctx form] (analyze ctx form (empty-env))) ([ctx form] (analyze ctx form (empty-env)))
([ctx form env] ([ctx form env]
(cond (cond
(form-literal? form) (const form) (form-literal? form) (const form)
(form-sym? form) (analyze-symbol ctx form env) (form-sym? form) (analyze-symbol ctx form env)
(form-vec? form) (vector-node (mapv #(analyze ctx % env) (form-vec-items form))) (form-vec? form) (with-coll-meta ctx form env
(form-map? form) (map-node (mapv (fn [p] [(analyze ctx (first p) env) (vector-node (mapv #(analyze ctx % env) (form-vec-items form))))
(form-map? form) (with-coll-meta ctx form env
(map-node (mapv (fn [p] [(analyze ctx (first p) env)
(analyze ctx (second p) env)]) (analyze ctx (second p) env)])
(form-map-pairs form))) (form-map-pairs form))))
(form-set? form) (set-node (mapv #(analyze ctx % env) (form-set-items form))) (form-set? form) (with-coll-meta ctx form env
(set-node (mapv #(analyze ctx % env) (form-set-items form))))
(form-list? form) (analyze-list ctx form env) (form-list? form) (analyze-list ctx form env)
;; regex literal #"…" -> a :regex IR node (leaf). The Chez back end emits a ;; regex literal #"…" -> a :regex IR node (leaf). The Chez back end emits a
;; jolt-regex value over the vendored irregex. ;; jolt-regex value over the vendored irregex.

View file

@ -8,12 +8,15 @@
;; The reader yields set literals as a FORM ({:jolt/type :jolt/set :value [...]}) ;; The reader yields set literals as a FORM ({:jolt/type :jolt/set :value [...]})
;; rather than a constructed set, so build the actual values, recursing into ;; rather than a constructed set, so build the actual values, recursing into
;; maps/vectors/lists. (Lists stay lists — EDN never evaluates them as code.) ;; maps/vectors/lists. (Lists stay lists — EDN never evaluates them as code.)
;; Re-attach the source value's metadata to each rebuilt collection — read-string
;; preserves reader metadata (^:ref […]) but this rebuild would otherwise drop it,
;; which a metadata-driven config lib (aero/integrant) relies on.
(defn- edn->value [opts x] (defn- edn->value [opts x]
(cond (cond
;; 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)) (set (map (fn [v] (edn->value opts v)) (get x :value))) (= :jolt/set (get x :jolt/type)) (with-meta (set (map (fn [v] (edn->value opts v)) (get x :value))) (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.
@ -27,12 +30,13 @@
custom (get (get opts :readers) tag-sym)] custom (get (get opts :readers) tag-sym)]
(cond (cond
custom (custom v) custom (custom v)
(get opts :default) ((get opts :default) tag v) ;; Clojure calls :default with the tag as a SYMBOL and the value.
(get opts :default) ((get opts :default) tag-sym v)
:else (__read-tagged tag v))) :else (__read-tagged tag v)))
(map? x) (map? x)
(into {} (map (fn [e] [(edn->value opts (key e)) (edn->value opts (val e))]) x)) (with-meta (into {} (map (fn [e] [(edn->value opts (key e)) (edn->value opts (val e))]) x)) (meta x))
(vector? x) (mapv (fn [v] (edn->value opts v)) x) (vector? x) (with-meta (mapv (fn [v] (edn->value opts v)) x) (meta x))
(seq? x) (map (fn [v] (edn->value opts v)) x) (seq? x) (with-meta (map (fn [v] (edn->value opts v)) x) (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

View file

@ -46,6 +46,14 @@
(defn n-error [] (:error @counters)) (defn n-error [] (:error @counters))
(defn failures [] (:fails @counters)) (defn failures [] (:fails @counters))
;; Message of a thrown value: ex-info's message, else a raw host condition's text
;; (ex-message is nil for those), else its printed form — so a crash is never
;; reported with a blank message.
(defn err-text [e]
(or (ex-message e)
(jolt.host/condition-message e)
(str e)))
;; clojure.test/report multimethod — present so suites that add reporting ;; clojure.test/report multimethod — present so suites that add reporting
;; methods (defmethod clojure.test/report :begin-test-var ...) load. The runner ;; methods (defmethod clojure.test/report :begin-test-var ...) load. The runner
;; below does its own console output and doesn't dispatch through it. ;; below does its own console output and doesn't dispatch through it.
@ -110,7 +118,7 @@
(clojure.test/inc-pass!) (clojure.test/inc-pass!)
(clojure.test/fail! (str (pr-str '~form) (when ~msg (str " — " ~msg))))) (clojure.test/fail! (str (pr-str '~form) (when ~msg (str " — " ~msg)))))
(catch Throwable e# (catch Throwable e#
(clojure.test/err! (str (pr-str '~form) " threw: " (clojure.core/ex-message e#)))))))) (clojure.test/err! (str (pr-str '~form) " threw: " (clojure.test/err-text e#))))))))
(defmacro testing [s & body] (defmacro testing [s & body]
`(do `(do
@ -152,7 +160,7 @@
(try (try
((:fn t)) ((:fn t))
(catch Throwable e (catch Throwable e
(err! (str (:name t) " crashed: " (clojure.core/ex-message e)))))))) (err! (str (:name t) " crashed: " (err-text e))))))))
(defn run-registered [] (defn run-registered []
(doseq [t @registry] (run-one t)) (doseq [t @registry] (run-one t))

View file

@ -5,19 +5,21 @@
[inner outer form] [inner outer form]
(cond (cond
; vectors/maps first so seq? can't swallow them (a vector is not seq? on ; vectors/maps first so seq? can't swallow them (a vector is not seq? on
; jolt, but keep the concrete branches authoritative) ; jolt, but keep the concrete branches authoritative). Re-attach the form's
(vector? form) (outer (vec (map inner form))) ; metadata to the rebuilt collection, as Clojure does — a metadata-driven walk
; (aero/spec) needs ^:ref and friends to survive the rebuild.
(vector? form) (outer (with-meta (vec (map inner form)) (meta form)))
; a record is also map?, but (empty record) yields a plain map — rebuild by ; a record is also map?, but (empty record) yields a plain map — rebuild by
; conj-ing the walked entries back onto the original so the record TYPE ; conj-ing the walked entries back onto the original so the record TYPE
; survives. Type-dispatched walks depend on it (e.g. integrant resolves ; survives. Type-dispatched walks depend on it (e.g. integrant resolves
; #ig/ref by detecting its Ref record while postwalking the config). ; #ig/ref by detecting its Ref record while postwalking the config).
(record? form) (outer (reduce (fn [r x] (conj r (inner x))) form form)) (record? form) (outer (reduce (fn [r x] (conj r (inner x))) form form))
(map? form) (outer (into (empty form) (map inner form))) (map? form) (outer (with-meta (into (empty form) (map inner form)) (meta form)))
; lists rebuild as lists, other seqs (incl. macro/template output: cons/ ; lists rebuild as lists, other seqs (incl. macro/template output: cons/
; concat/lazy-seq) walk too — without this, postwalk-replace silently no-op'd ; concat/lazy-seq) walk too — without this, postwalk-replace silently no-op'd
; a quoted list, breaking clojure.template/apply-template ; a quoted list, breaking clojure.template/apply-template
(list? form) (outer (apply list (map inner form))) (list? form) (outer (with-meta (apply list (map inner form)) (meta form)))
(seq? form) (outer (map inner form)) (seq? form) (outer (with-meta (map inner form) (meta form)))
:else (outer form))) :else (outer form)))
(defn postwalk (defn postwalk

View file

@ -1,5 +1,5 @@
(ns app.core (ns app.core
(:require [app.util :as util] (:require [app.util :as util :refer [greet]]
[clojure.java.io :as io])) [clojure.java.io :as io]))
;; An aliased cross-ns defmethod: 'util/greet is passed quoted to defmethod-setup, ;; An aliased cross-ns defmethod: 'util/greet is passed quoted to defmethod-setup,
@ -7,6 +7,10 @@
;; ns "util" and never reaches app.util/greet (the dispatch falls to :default). ;; ns "util" and never reaches app.util/greet (the dispatch falls to :default).
(defmethod util/greet :loud [_] "greet:loud") (defmethod util/greet :loud [_] "greet:loud")
;; A defmethod on a REFERRED multifn (bare `greet`): the AOT build must register
;; the :refer so the bare name resolves to app.util/greet, not a shadow.
(defmethod greet :soft [_] "greet:soft")
(defn -main [& args] (defn -main [& args]
;; the resource is baked into the binary (deps.edn :jolt/build :embed), so this ;; the resource is baked into the binary (deps.edn :jolt/build :embed), so this
;; resolves with no resources/ dir on disk, run from any cwd. ;; resolves with no resources/ dir on disk, run from any cwd.
@ -15,4 +19,5 @@
(println "args:" (vec args)) (println "args:" (vec args))
(println "sum:" (reduce + (map count args))) (println "sum:" (reduce + (map count args)))
(println "greet-default:" (util/greet :unknown)) (println "greet-default:" (util/greet :unknown))
(println "greet-loud:" (util/greet :loud))) (println "greet-loud:" (util/greet :loud))
(println "greet-soft:" (util/greet :soft)))

View file

@ -1046,6 +1046,23 @@
{:suite "metadata / type hints" :label "type hint in destructure" :expected "3" :actual "(let [{:keys [^long a]} {:a 3}] a)"} {:suite "metadata / type hints" :label "type hint in destructure" :expected "3" :actual "(let [{:keys [^long a]} {:a 3}] a)"}
{:suite "metadata / type hints" :label "symbol hint -> :tag" :expected "\"String\"" :actual "(:tag (meta (read-string \"^String x\")))"} {:suite "metadata / type hints" :label "symbol hint -> :tag" :expected "\"String\"" :actual "(:tag (meta (read-string \"^String x\")))"}
{:suite "metadata / type hints" :label "keyword hint -> true" :expected "true" :actual "(:foo (meta (read-string \"^:foo x\")))"} {:suite "metadata / type hints" :label "keyword hint -> true" :expected "true" :actual "(:foo (meta (read-string \"^:foo x\")))"}
{:suite "metadata / read data metadata" :label "vector data meta" :expected "{:ref true}" :actual "(meta (read-string \"^:ref [:greeting]\"))"}
{:suite "metadata / read data metadata" :label "map data meta" :expected "{:k 1}" :actual "(meta (read-string \"^{:k 1} {:a 2}\"))"}
{:suite "metadata / read data metadata" :label "set data meta" :expected "{:s true}" :actual "(meta (read-string \"^:s #{1 2}\"))"}
{:suite "metadata / read data metadata" :label "nested vector data meta" :expected "{:x true}" :actual "(meta (second (read-string \"[1 ^:x {:a 2}]\")))"}
{:suite "metadata / read data metadata" :label "collection value unchanged" :expected "[:greeting]" :actual "(read-string \"^:ref [:greeting]\")"}
{:suite "metadata / coll literal in code keeps meta" :label "vector literal meta" :expected "{:foo true}" :actual "(meta ^:foo [1 2])"}
{:suite "metadata / coll literal in code keeps meta" :label "map literal meta with expr" :expected "{:a 3}" :actual "(meta ^{:a (+ 1 2)} [1])"}
{:suite "metadata / coll literal in code keeps meta" :label "map literal value-eval order" :expected "[1 2]" :actual "(let [a (atom [])] ^:t {(do (swap! a conj 1) :a) 1 (do (swap! a conj 2) :b) 2} @a)"}
{:suite "metadata / *print-meta*" :label "pr-str prefixes ^meta" :expected "\"^{:ref true} [:g]\"" :actual "(binding [*print-meta* true] (pr-str (with-meta [:g] {:ref true})))"}
{:suite "metadata / *print-meta*" :label "off by default" :expected "\"[:g]\"" :actual "(pr-str (with-meta [:g] {:ref true}))"}
{:suite "metadata / *print-meta*" :label "symbol meta prefixed" :expected "\"^{:k 1} x\"" :actual "(binding [*print-meta* true] (pr-str (with-meta (quote x) {:k 1})))"}
{:suite "metadata / *print-meta*" :label "print then read round-trips" :expected "{:ref true}" :actual "(meta (read-string (binding [*print-meta* true] (pr-str (with-meta [:g] {:ref true})))))"}
{:suite "metadata / clojure.walk preserves meta" :label "postwalk keeps vector meta" :expected "{:ref true}" :actual "(do (require (quote [clojure.walk :as w])) (meta (w/postwalk identity (with-meta [:b] {:ref true}))))"}
{:suite "metadata / clojure.walk preserves meta" :label "postwalk keeps map meta" :expected "{:m 1}" :actual "(do (require (quote [clojure.walk :as w])) (meta (w/postwalk identity (with-meta {:a 2} {:m 1}))))"}
{:suite "metadata / clojure.edn preserves meta" :label "edn vector meta" :expected "{:ref true}" :actual "(do (require (quote [clojure.edn :as e1])) (meta (e1/read-string \"^:ref [:greeting]\")))"}
{:suite "metadata / clojure.edn preserves meta" :label "edn map meta" :expected "{:m true}" :actual "(do (require (quote [clojure.edn :as e1])) (meta (e1/read-string \"^:m {:a 1}\")))"}
{:suite "metadata / clojure.edn preserves meta" :label "edn set meta" :expected "{:s true}" :actual "(do (require (quote [clojure.edn :as e1])) (meta (e1/read-string \"^:s #{1}\")))"}
{:suite "metadata / def metadata" :label "^:dynamic var binds" :expected "9" :actual "(do (def ^:dynamic *d* 1) (binding [*d* 9] *d*))"} {:suite "metadata / def metadata" :label "^:dynamic var binds" :expected "9" :actual "(do (def ^:dynamic *d* 1) (binding [*d* 9] *d*))"}
{:suite "metadata / def metadata" :label "^:private on var" :expected "true" :actual "(do (def ^:private pv 1) (:private (meta (var pv))))"} {:suite "metadata / def metadata" :label "^:private on var" :expected "true" :actual "(do (def ^:private pv 1) (:private (meta (var pv))))"}
{:suite "metadata / def metadata" :label "^Type tag on var" :expected "java.lang.String" :actual "(do (def ^String tv \"a\") (:tag (meta (var tv))))"} {:suite "metadata / def metadata" :label "^Type tag on var" :expected "java.lang.String" :actual "(do (def ^String tv \"a\") (:tag (meta (var tv))))"}
@ -2645,6 +2662,20 @@
{:suite "clojure.walk / records keep their type" :label "instance? survives a walk" :expected "true" :actual "(do (require (quote [clojure.walk :as w])) (defrecord R [a]) (instance? R (w/postwalk identity (->R 1))))"} {:suite "clojure.walk / records keep their type" :label "instance? survives a walk" :expected "true" :actual "(do (require (quote [clojure.walk :as w])) (defrecord R [a]) (instance? R (w/postwalk identity (->R 1))))"}
{:suite "clojure.walk / records keep their type" :label "a record nested in a map keeps its type" :expected "true" :actual "(do (require (quote [clojure.walk :as w])) (defrecord R [a]) (record? (:r (w/postwalk identity {:r (->R 1)}))))"} {:suite "clojure.walk / records keep their type" :label "a record nested in a map keeps its type" :expected "true" :actual "(do (require (quote [clojure.walk :as w])) (defrecord R [a]) (record? (:r (w/postwalk identity {:r (->R 1)}))))"}
{:suite "clojure.walk / records keep their type" :label "prewalk preserves record type" :expected "true" :actual "(do (require (quote [clojure.walk :as w])) (defrecord R [a]) (record? (w/prewalk identity (->R 1))))"} {:suite "clojure.walk / records keep their type" :label "prewalk preserves record type" :expected "true" :actual "(do (require (quote [clojure.walk :as w])) (defrecord R [a]) (record? (w/prewalk identity (->R 1))))"}
{:suite "reader / auto-resolved keywords" :label "::kw is namespace-qualified" :expected "true" :actual "(some? (namespace ::foo))"}
{:suite "reader / auto-resolved keywords" :label "::kw keeps its name" :expected "\"foo\"" :actual "(name ::foo)"}
{:suite "reader / auto-resolved keywords" :label "::kw resolves to the current ns" :expected "true" :actual "(= ::a ::a)"}
{:suite "clojure.edn / reader opts" :label ":default receives the tag as a symbol" :expected "[true \"foo\" 5]" :actual "(do (require (quote [clojure.edn :as edn])) (let [r (edn/read-string {:default (fn [t v] [t v])} \"#foo 5\")] [(symbol? (first r)) (name (first r)) (second r)]))"}
{:suite "metadata / lazy seqs carry meta" :label "with-meta on a lazy seq" :expected "{:k 1}" :actual "(meta (with-meta (map inc [1 2 3]) {:k 1}))"}
{:suite "dynamic vars / *print-meta*" :label "*print-meta* is a bindable dynamic var" :expected "true" :actual "(binding [*print-meta* true] (true? *print-meta*))"}
{:suite "tagged literals / value equality" :label "equal tag+form are =" :expected "true" :actual "(= (tagged-literal (quote x) [1 2]) (tagged-literal (quote x) [1 2]))"}
{:suite "tagged literals / value equality" :label "different tag is not =" :expected "false" :actual "(= (tagged-literal (quote x) [1]) (tagged-literal (quote y) [1]))"}
{:suite "tagged literals / value equality" :label "dedup as map keys" :expected "1" :actual "(count {(tagged-literal (quote x) [1]) :a (tagged-literal (quote x) [1]) :b})"}
{:suite "interop / clojure.lang interfaces" :label "vector is IObj" :expected "true" :actual "(instance? clojure.lang.IObj [1])"}
{:suite "interop / clojure.lang interfaces" :label "map entry is IMapEntry" :expected "true" :actual "(instance? clojure.lang.IMapEntry (first {:a 1}))"}
{:suite "interop / clojure.lang interfaces" :label "record is IRecord" :expected "true" :actual "(do (defrecord R [a]) (instance? clojure.lang.IRecord (->R 1)))"}
{:suite "interop / clojure.lang interfaces" :label "number is not IObj" :expected "false" :actual "(instance? clojure.lang.IObj 5)"}
{:suite "reader / default data readers" :label "#inst is in default-data-readers" :expected "true" :actual "(boolean (get default-data-readers (quote inst)))"}
{:suite "conformance / CRITICAL: lazy sequences" :label "self-ref lazy-cat fib" :expected "[0 1 1 2 3 5 8 13 21 34]" :actual "(do (def fib-seq (lazy-cat [0 1] (map + (rest fib-seq) fib-seq))) (take 10 fib-seq))"} {:suite "conformance / CRITICAL: lazy sequences" :label "self-ref lazy-cat fib" :expected "[0 1 1 2 3 5 8 13 21 34]" :actual "(do (def fib-seq (lazy-cat [0 1] (map + (rest fib-seq) fib-seq))) (take 10 fib-seq))"}
{:suite "conformance / CRITICAL: multi-collection map" :label "map two colls" :expected "[11 22 33]" :actual "(map + [1 2 3] [10 20 30])"} {:suite "conformance / CRITICAL: multi-collection map" :label "map two colls" :expected "[11 22 33]" :actual "(map + [1 2 3] [10 20 30])"}
{:suite "conformance / CRITICAL: multi-collection map" :label "map three colls" :expected "[12 24 36]" :actual "(map + [1 2 3] [10 20 30] [1 2 3])"} {:suite "conformance / CRITICAL: multi-collection map" :label "map three colls" :expected "[12 24 36]" :actual "(map + [1 2 3] [10 20 30] [1 2 3])"}

View file

@ -303,7 +303,7 @@
{:suite "reader" :expr "(integer? (read-string \"7\"))" :expected "true"} {:suite "reader" :expr "(integer? (read-string \"7\"))" :expected "true"}
{:suite "reader" :expr "(= :foo (read-string \":foo\"))" :expected "true"} {:suite "reader" :expr "(= :foo (read-string \":foo\"))" :expected "true"}
{:suite "reader" :expr "(= :a/b (read-string \":a/b\"))" :expected "true"} {:suite "reader" :expr "(= :a/b (read-string \":a/b\"))" :expected "true"}
{:suite "reader" :expr "(= :foo (read-string \"::foo\"))" :expected "true"} {:suite "reader" :expr "(= [true \"foo\"] [(some? (namespace (read-string \"::foo\"))) (name (read-string \"::foo\"))])" :expected "true"}
{:suite "reader" :expr "(= (quote sym) (read-string \"sym\"))" :expected "true"} {:suite "reader" :expr "(= (quote sym) (read-string \"sym\"))" :expected "true"}
{:suite "reader" :expr "(= (quote ns/sym) (read-string \"ns/sym\"))" :expected "true"} {:suite "reader" :expr "(= (quote ns/sym) (read-string \"ns/sym\"))" :expected "true"}
{:suite "reader" :expr "(nil? (read-string \"nil\"))" :expected "true"} {:suite "reader" :expr "(nil? (read-string \"nil\"))" :expected "true"}