Dead-code removal, perf fixes, deterministic seed emission

Round 1 (correctness + dead code):
- Fix duplicate java.util.HashMap registration in host-static.ss: the alist
  impl shadowed the hashtable ctor while leaving the hashtable methods bound,
  so .keySet/.values/.remove/.clear crashed. Drop the alist version.
- Delete jolt-core/jolt/reader.clj: a 463-line dead duplicate reader, never
  required or compiled (the live reader is host/chez/reader.ss) and drifted.
- Remove dead defs: ir/rt + :rt op + unused ir/op; the Janet branch in
  clojure.edn/drain-reader; a shadowed first clojure.string/trim-newline;
  io.ss jolt-char-array + the reader def-var (both shadowed by natives-array);
  concurrency.ss jolt-future-done?*; compile-eval.ss jolt-analyze-emit.

Round 2 (perf + determinism):
- emit-quoted-map-value / quoted sets now emit sorted by emitted text instead
  of host-hash order, which isn't stable across Chez versions (jolt-8479).
- jolt-into folds through a transient, so into/vec/mapv/filterv onto a vector
  are O(n) instead of O(n^2).
- deps resolve-deps walks its queue with an index cursor (was subvec-per-pop).
- async channel and agent action queues use amortized-O(1) FIFOs; ArrayList is
  backed by a growable vector (O(1) add/get) instead of a list.
This commit is contained in:
Yogthos 2026-06-23 01:05:45 -04:00
parent adafe04072
commit e93006b4be
14 changed files with 199 additions and 656 deletions

View file

@ -21,20 +21,39 @@
(define (jolt-async-sliding-buffer n) (make-async-buffer n 'sliding))
;; --- channels ---------------------------------------------------------------
;; items: a FIFO list of (value . box); box is #f for a buffered value or a 1-slot
;; vector for an unbuffered rendezvous put (set #t when taken, waking the putter).
;; items: an amortized-O(1) FIFO held as a mutable #(out in len) — `out` is the
;; front (pop from its head), `in` holds pushed entries reversed onto it, `len` is
;; the count (an append-to-a-list FIFO is O(n) per push and O(n) to measure).
;; Each entry is (value . box); box is #f for a buffered value or a 1-slot vector
;; for an unbuffered rendezvous put (set #t when taken, waking the putter).
;; cap 0 + kind 'unbuffered = rendezvous; cap>0 with kind fixed/dropping/sliding.
(define-record-type async-chan
(fields mu cv (mutable items) cap kind (mutable closed?) (mutable xrf))
(nongenerative async-chan-v1))
(define (ac-qlen ch) (length (async-chan-items ch)))
(define (ac-qempty? ch) (null? (async-chan-items ch)))
(define (ac-qpush! ch entry) (async-chan-items-set! ch (append (async-chan-items ch) (list entry))))
(define (ac-qnew) (vector '() '() 0))
(define (ac-qlen ch) (vector-ref (async-chan-items ch) 2))
(define (ac-qempty? ch) (fx=? 0 (vector-ref (async-chan-items ch) 2)))
(define (ac-qpush! ch entry)
(let ((q (async-chan-items ch)))
(vector-set! q 1 (cons entry (vector-ref q 1)))
(vector-set! q 2 (fx+ 1 (vector-ref q 2)))))
(define (ac-qfront! q) ; ensure `out` is non-empty: out := reverse in
(when (null? (vector-ref q 0))
(vector-set! q 0 (reverse (vector-ref q 1)))
(vector-set! q 1 '())))
(define (ac-qpop! ch)
(let ((e (car (async-chan-items ch))))
(async-chan-items-set! ch (cdr (async-chan-items ch))) e))
(define (ac-qdrop-oldest! ch) (async-chan-items-set! ch (cdr (async-chan-items ch))))
(let ((q (async-chan-items ch)))
(ac-qfront! q)
(let ((out (vector-ref q 0)))
(vector-set! q 0 (cdr out))
(vector-set! q 2 (fx- (vector-ref q 2) 1))
(car out))))
(define (ac-qdrop-oldest! ch)
(let ((q (async-chan-items ch)))
(ac-qfront! q)
(vector-set! q 0 (cdr (vector-ref q 0)))
(vector-set! q 2 (fx- (vector-ref q 2) 1))))
;; enqueue honoring the buffer kind (used by the transducer step + buffered puts).
(define (ac-buf-give! ch v)
@ -54,7 +73,7 @@
((null? (cdr args)) (car args)) ; completion
(else (ac-buf-give! ch (cadr args)) (car args))))) ; step
(define (ac-make cap kind xrf) (make-async-chan (make-mutex) (make-condition) '() cap kind #f xrf))
(define (ac-make cap kind xrf) (make-async-chan (make-mutex) (make-condition) (ac-qnew) cap kind #f xrf))
;; (chan) | (chan n) | (chan buf) | (chan n|buf xform)
(define (jolt-async-chan . args)

View file

@ -62,10 +62,6 @@
(ir (jolt-ce-analyze ctx form)))
(jolt-ce-emit ir)))
;; Source string -> Scheme source string (read then analyze -> emit, all on Chez).
(define (jolt-analyze-emit src ns)
(jolt-analyze-emit-form (jolt-ce-read src) ns))
;; --- runtime defmacro -------------------------------------------------------
;; Shared with emit-image.ss (loaded after this). A defmacro lowers to a def of
;; its expander fn + a macro flag, exactly as the prelude emits build-time macros.

View file

@ -96,7 +96,6 @@
#t)))))
cancelled))
(define (jolt-future-done?* f) (and (jolt-future? f) (jolt-future-done? f)))
(define (jolt-native-future-done? x)
(if (jolt-future? x) (jolt-future-done? x)
(jolt-throw (jolt-ex-info "future-done? requires a future" (jolt-hash-map)))))
@ -158,22 +157,34 @@
(let loop ((o opts) (validator jolt-nil))
(cond
((or (null? o) (null? (cdr o)))
(make-jolt-agent state jolt-nil validator '() #f (make-mutex) (make-condition)))
(make-jolt-agent state jolt-nil validator (vector '() '()) #f (make-mutex) (make-condition)))
((and (keyword-t? (car o)) (string=? (keyword-t-name (car o)) "validator"))
(loop (cddr o) (cadr o)))
(else (loop (cddr o) validator)))))
;; The action queue is an amortized-O(1) FIFO held as a mutable #(out in): `out` is
;; the front, `in` holds sends reversed onto it (an append-to-a-list send was O(n)).
;; All three helpers run under the agent mutex.
(define (jagent-q-empty? a)
(let ((q (jolt-agent-queue a))) (and (null? (vector-ref q 0)) (null? (vector-ref q 1)))))
(define (jagent-q-push! a entry)
(let ((q (jolt-agent-queue a))) (vector-set! q 1 (cons entry (vector-ref q 1)))))
(define (jagent-q-pop! a)
(let ((q (jolt-agent-queue a)))
(when (null? (vector-ref q 0))
(vector-set! q 0 (reverse (vector-ref q 1))) (vector-set! q 1 '()))
(let ((out (vector-ref q 0))) (vector-set! q 0 (cdr out)) (car out))))
;; Drain the queue, applying each action (f state arg*) outside the lock (an action
;; may send/deref the same agent). A validator rejection or a thrown action puts the
;; agent in an error state and halts the queue (JVM :fail mode).
(define (jolt-agent-worker a)
(let loop ()
(let ((act (with-mutex (jolt-agent-mu a)
(if (or (not (jolt-nil? (jolt-agent-err a))) (null? (jolt-agent-queue a)))
(if (or (not (jolt-nil? (jolt-agent-err a))) (jagent-q-empty? a))
(begin (jolt-agent-running?-set! a #f)
(condition-broadcast (jolt-agent-cv a)) #f)
(let ((x (car (jolt-agent-queue a))))
(jolt-agent-queue-set! a (cdr (jolt-agent-queue a))) x)))))
(jagent-q-pop! a)))))
(when act
(guard (e (#t (with-mutex (jolt-agent-mu a)
(jolt-agent-err-set! a e)
@ -190,7 +201,7 @@
;; the JVM's fixed/cached pool split.)
(define (jolt-agent-send a f . args)
(with-mutex (jolt-agent-mu a)
(jolt-agent-queue-set! a (append (jolt-agent-queue a) (list (cons f args))))
(jagent-q-push! a (cons f args))
(unless (jolt-agent-running? a)
(jolt-agent-running?-set! a #t)
(fork-thread (lambda () (jolt-agent-worker a)))))
@ -202,7 +213,7 @@
(lambda (a)
(with-mutex (jolt-agent-mu a)
(let loop ()
(when (or (jolt-agent-running? a) (pair? (jolt-agent-queue a)))
(when (or (jolt-agent-running? a) (not (jagent-q-empty? a)))
(condition-wait (jolt-agent-cv a) (jolt-agent-mu a)) (loop)))))
agents)
jolt-nil)

View file

@ -427,12 +427,41 @@
(register-class-ctor! "Object" (lambda _ (make-jhost "object" (vector))))
;; ---- java.util.ArrayList ----------------------------------------------------
;; A mutable list backed by a Scheme list in a box. medley's stateful transducers
;; (window / partition-between) build one with .add / .size / .toArray / .clear /
;; .remove. (ArrayList.) | (ArrayList. n) | (ArrayList. coll).
(define (al-list self) (vector-ref (jhost-state self) 0))
(define (al-set! self xs) (vector-set! (jhost-state self) 0 xs))
(define (make-arraylist xs) (make-jhost "arraylist" (vector xs)))
;; A mutable list backed by a growable Scheme vector. State is #(backing count);
;; .add amortizes O(1) and .get is O(1) (a list backing made both O(n)). medley's
;; stateful transducers (window / partition-between) build one with .add / .size /
;; .toArray / .clear / .remove. (ArrayList.) | (ArrayList. n) | (ArrayList. coll).
(define al-min-cap 8)
(define (al-vec self) (vector-ref (jhost-state self) 0))
(define (al-cnt self) (vector-ref (jhost-state self) 1))
(define (al-cnt! self n) (vector-set! (jhost-state self) 1 n))
(define (make-arraylist xs) ; xs: a Scheme list of initial elements
(let* ((n (length xs)) (cap (fxmax al-min-cap n)) (v (make-vector cap jolt-nil)))
(let loop ((i 0) (xs xs)) (when (pair? xs) (vector-set! v i (car xs)) (loop (fx+ i 1) (cdr xs))))
(make-jhost "arraylist" (vector v n))))
(define (al-ensure! self need) ; grow the backing vector (doubling) to fit `need`
(let ((v (al-vec self)))
(when (fx>? need (vector-length v))
(let grow ((cap (fxmax al-min-cap (vector-length v))))
(if (fx<? cap need) (grow (fx* cap 2))
(let ((nv (make-vector cap jolt-nil)))
(let cp ((i 0)) (when (fx<? i (al-cnt self)) (vector-set! nv i (vector-ref v i)) (cp (fx+ i 1))))
(vector-set! (jhost-state self) 0 nv)))))))
(define (al-push! self x)
(let ((n (al-cnt self))) (al-ensure! self (fx+ n 1)) (vector-set! (al-vec self) n x) (al-cnt! self (fx+ n 1))))
(define (al-insert-at! self i x)
(let ((n (al-cnt self)))
(al-ensure! self (fx+ n 1))
(let ((v (al-vec self)))
(let shift ((j n)) (when (fx>? j i) (vector-set! v j (vector-ref v (fx- j 1))) (shift (fx- j 1))))
(vector-set! v i x) (al-cnt! self (fx+ n 1)))))
(define (al-remove-at! self i)
(let ((n (al-cnt self)) (v (al-vec self)))
(let shift ((j i)) (when (fx<? j (fx- n 1)) (vector-set! v j (vector-ref v (fx+ j 1))) (shift (fx+ j 1))))
(vector-set! v (fx- n 1) jolt-nil) (al-cnt! self (fx- n 1))))
(define (al->list self) ; first `count` elements as a Scheme list
(let ((v (al-vec self)))
(let loop ((i (fx- (al-cnt self) 1)) (acc '())) (if (fx<? i 0) acc (loop (fx- i 1) (cons (vector-ref v i) acc))))))
(register-class-ctor! "ArrayList"
(lambda args
(cond ((null? args) (make-arraylist '()))
@ -443,34 +472,28 @@
(cond ((null? args) (make-arraylist '()))
((number? (car args)) (make-arraylist '()))
(else (make-arraylist (seq->list (jolt-seq (car args))))))))
(define (al-remove-at xs i)
(let loop ((xs xs) (i i) (acc '()))
(cond ((null? xs) (reverse acc))
((= i 0) (append (reverse acc) (cdr xs)))
(else (loop (cdr xs) (- i 1) (cons (car xs) acc))))))
(register-host-methods! "arraylist"
(list
(cons "add" (lambda (self . a)
;; (.add x) -> append+true; (.add i x) -> insert at i, returns nil.
(if (= 1 (length a))
(begin (al-set! self (append (al-list self) (list (car a)))) #t)
(let ((i (jnum->exact (car a))) (x (cadr a)) (xs (al-list self)))
(al-set! self (append (list-head xs i) (list x) (list-tail xs i))) jolt-nil))))
(cons "add!" (lambda (self x) (al-set! self (append (al-list self) (list x))) #t))
(cons "get" (lambda (self i) (list-ref (al-list self) (jnum->exact i))))
(begin (al-push! self (car a)) #t)
(begin (al-insert-at! self (jnum->exact (car a)) (cadr a)) jolt-nil))))
(cons "add!" (lambda (self x) (al-push! self x) #t))
(cons "get" (lambda (self i) (vector-ref (al-vec self) (jnum->exact i))))
(cons "set" (lambda (self i x)
(let* ((xs (al-list self)) (idx (jnum->exact i)) (old (list-ref xs idx)))
(al-set! self (append (list-head xs idx) (list x) (list-tail xs (+ idx 1)))) old)))
(cons "size" (lambda (self) (->num (length (al-list self)))))
(cons "isEmpty" (lambda (self) (null? (al-list self))))
(let* ((idx (jnum->exact i)) (old (vector-ref (al-vec self) idx)))
(vector-set! (al-vec self) idx x) old)))
(cons "size" (lambda (self) (->num (al-cnt self))))
(cons "isEmpty" (lambda (self) (fx=? 0 (al-cnt self))))
(cons "remove" (lambda (self i)
(let* ((xs (al-list self)) (idx (jnum->exact i)) (old (list-ref xs idx)))
(al-set! self (al-remove-at xs idx)) old)))
(cons "clear" (lambda (self) (al-set! self '()) jolt-nil))
(cons "contains" (lambda (self x) (and (memp (lambda (e) (jolt=2 e x)) (al-list self)) #t)))
(cons "toArray" (lambda (self . _) (apply jolt-vector (al-list self))))
(cons "iterator" (lambda (self) (make-jiterator (list->cseq (al-list self)))))
(cons "toString" (lambda (self) (jolt-pr-str (list->cseq (al-list self)))))))
(let* ((idx (jnum->exact i)) (old (vector-ref (al-vec self) idx)))
(al-remove-at! self idx) old)))
(cons "clear" (lambda (self) (vector-set! (jhost-state self) 0 (make-vector al-min-cap jolt-nil)) (al-cnt! self 0) jolt-nil))
(cons "contains" (lambda (self x) (and (memp (lambda (e) (jolt=2 e x)) (al->list self)) #t)))
(cons "toArray" (lambda (self . _) (apply jolt-vector (al->list self))))
(cons "iterator" (lambda (self) (make-jiterator (list->cseq (al->list self)))))
(cons "toString" (lambda (self) (jolt-pr-str (list->cseq (al->list self)))))))
(register-class-ctor! "StringBuilder"
(lambda args (make-jhost "string-builder"
@ -631,32 +654,6 @@
jolt-nil))
(cons "close" (lambda (self) jolt-nil))))
;; ---- HashMap ----------------------------------------------------------------
;; state: a box holding an alist of (k . v), jolt= keyed.
(define (hm-alist self) (vector-ref (jhost-state self) 0))
(define (hm-set! self al) (vector-set! (jhost-state self) 0 al))
(define (hm-assoc al k v)
(let loop ((ps al) (acc '()) (hit #f))
(cond ((null? ps) (reverse (if hit acc (cons (cons k v) acc))))
((jolt=2 (caar ps) k) (loop (cdr ps) (cons (cons k v) acc) #t))
(else (loop (cdr ps) (cons (car ps) acc) hit)))))
(define (hm-get al k) (let loop ((ps al)) (cond ((null? ps) jolt-nil) ((jolt=2 (caar ps) k) (cdar ps)) (else (loop (cdr ps))))))
(define (coll->pairs m)
(if (jolt-nil? m) '()
(let loop ((s (jolt-seq m)) (acc '()))
(if (jolt-nil? s) (reverse acc)
(let ((e (seq-first s))) (loop (jolt-seq (seq-more s)) (cons (cons (jolt-nth e 0) (jolt-nth e 1)) acc)))))))
(register-class-ctor! "HashMap"
(lambda args
(let ((init (and (pair? args) (car args))))
(make-jhost "hashmap" (vector (if (and init (not (number? init))) (coll->pairs init) '()))))))
(register-host-methods! "hashmap"
(list (cons "get" (lambda (self k) (hm-get (hm-alist self) k)))
(cons "put" (lambda (self k v) (hm-set! self (hm-assoc (hm-alist self) k v)) v))
(cons "putAll" (lambda (self m) (for-each (lambda (p) (hm-set! self (hm-assoc (hm-alist self) (car p) (cdr p)))) (coll->pairs m)) jolt-nil))
(cons "containsKey" (lambda (self k) (not (jolt-nil? (hm-get (hm-alist self) k)))))
(cons "size" (lambda (self) (->num (length (hm-alist self)))))))
;; ---- StringTokenizer --------------------------------------------------------
;; state: a vector #(tokens-list pos)
(define (tokenize s delims)

View file

@ -261,18 +261,6 @@
(def-var! "clojure.core" "spit" jolt-spit)
(def-var! "clojure.core" "flush" jolt-flush)
;; --- char-array: a seq of chars over a string (the JVM char[]). io/reader's
;; char[] branch + selmer's (char-array template) feed on this.
;; char-array (string -> chars). A leaf array native; lives here as io/reader
;; is its only Chez consumer so far.
(define (jolt-char-array a . rest)
(cond
((string? a) (list->cseq (string->list a)))
((number? a) (list->cseq (make-list (exact (truncate a)) #\nul)))
(else (list->cseq (map (lambda (c) (if (char? c) c (integer->char (exact (truncate c)))))
(seq->list a))))))
(def-var! "clojure.core" "char-array" jolt-char-array)
;; --- with-open's close seam (__close): a map-like value closes via its :close
;; fn; a jhost reader/writer/file via its .close method (a no-op here); anything
;; else is an error.
@ -323,7 +311,8 @@
;; --- clojure.java.io ns -----------------------------------------------------
(def-var! "clojure.java.io" "file" jolt-make-file)
(def-var! "clojure.java.io" "as-file" (lambda (x) (if (jfile? x) x (make-jfile (file-path-of x)))))
(def-var! "clojure.java.io" "reader" jolt-io-reader)
;; "reader" is bound by natives-array.ss (loaded later) so a char[] argument is
;; handled; that binding delegates here via jolt-io-reader for everything else.
(def-var! "clojure.java.io" "writer" jolt-io-writer)
(def-var! "clojure.java.io" "input-stream" jolt-io-reader)
(def-var! "clojure.java.io" "output-stream" jolt-io-writer)

File diff suppressed because one or more lines are too long

View file

@ -1063,7 +1063,7 @@
(guard (e (#t #f))
(def-var! "clojure.edn" "read-string" (letrec ((read-string (case-lambda ((s) (let fnrec93 ((s s)) (jolt-invoke (var-deref "clojure.edn" "read-edn") (jolt-hash-map) s))) ((opts s) (let fnrec94 ((opts opts) (s s)) (jolt-invoke (var-deref "clojure.edn" "read-edn") opts s)))))) read-string)))
(guard (e (#t #f))
(def-var! "clojure.edn" "drain-reader" (letrec ((drain-reader (lambda (reader) (let fnrec95 ((reader reader)) (if (jolt= (keyword "core" "file") (host-static-call "janet" "type" reader)) (host-static-call "janet.file" "read" reader (keyword #f "all")) (let* ((acc (jolt-invoke (var-deref "clojure.core" "transient") (jolt-vector))) (c (record-method-dispatch reader "read" (jolt-vector)))) (let loop96 ((acc acc) (c c)) (if (jolt-truthy? (jolt-invoke (var-deref "clojure.core" "==") -1 c)) (jolt-apply (var-deref "clojure.core" "str") (jolt-map (var-deref "clojure.core" "char") (jolt-invoke (var-deref "clojure.core" "persistent!") acc))) (let* ((_a$97 (jolt-invoke (var-deref "clojure.core" "conj!") acc c)) (_a$98 (record-method-dispatch reader "read" (jolt-vector)))) (loop96 _a$97 _a$98)))))))))) drain-reader)))
(def-var! "clojure.edn" "drain-reader" (letrec ((drain-reader (lambda (reader) (let fnrec95 ((reader reader)) (let* ((acc (jolt-invoke (var-deref "clojure.core" "transient") (jolt-vector))) (c (record-method-dispatch reader "read" (jolt-vector)))) (let loop96 ((acc acc) (c c)) (if (jolt-truthy? (jolt-invoke (var-deref "clojure.core" "==") -1 c)) (jolt-apply (var-deref "clojure.core" "str") (jolt-map (var-deref "clojure.core" "char") (jolt-invoke (var-deref "clojure.core" "persistent!") acc))) (let* ((_a$97 (jolt-invoke (var-deref "clojure.core" "conj!") acc c)) (_a$98 (record-method-dispatch reader "read" (jolt-vector)))) (loop96 _a$97 _a$98))))))))) drain-reader)))
(guard (e (#t #f))
(def-var! "clojure.edn" "read" (letrec ((read (case-lambda ((reader) (let fnrec99 ((reader reader)) (jolt-invoke read (jolt-hash-map) reader))) ((opts reader) (let fnrec100 ((opts opts) (reader reader)) (jolt-invoke (var-deref "clojure.edn" "read-edn") opts (jolt-invoke (var-deref "clojure.edn" "drain-reader") reader))))))) read)))
(guard (e (#t #f))

View file

@ -206,7 +206,13 @@
(if (jolt-reduced? r) (jolt-reduced-val r) r))))
(else (reduce-seq f init (jolt-seq coll)))))))
(define (jolt-into to from) (reduce-seq (lambda (acc x) (jolt-conj1 acc x)) to (jolt-seq from)))
;; Fold through a transient so a pvec/pmap/pset target is built in O(n): a
;; persistent pvec-conj copies its whole backing vector each step, making a naive
;; fold O(n^2) (and into/vec/mapv/filterv all route here). jolt-transient-new
;; falls back to a copy-on-write wrapper for other targets (lists, sorted colls,
;; nil), so those keep the old per-step jolt-conj behaviour.
(define (jolt-into to from)
(jolt-persistent! (reduce-seq (lambda (t x) (jolt-conj! t x)) (jolt-transient-new to) (jolt-seq from))))
(define (range-from n) (cseq-lazy n (lambda () (range-from (+ n 1)))))
(define (range-bounded n end step)

View file

@ -210,10 +210,12 @@
(str/join " " (mapcat (fn [p] [(emit-quoted (nth p 0)) (emit-quoted (nth p 1))]) pairs))
")"))
(defn- emit-quoted-map-value [m]
;; a jolt map VALUE (def/symbol metadata is a value, not a reader form)
(str "(jolt-hash-map "
(str/join " " (mapcat (fn [k] [(emit-quoted k) (emit-quoted (get m k))]) (keys m)))
")"))
;; A jolt map VALUE (def/symbol metadata is a value, not a reader form). (keys m)
;; iterates in host-hash order, which is not stable across Chez versions, so emit
;; the pairs sorted by their emitted Scheme text — keeps the seed byte-fixed
;; regardless of the host hash (jolt-8479).
(let [pairs (sort (map (fn [k] (str (emit-quoted k) " " (emit-quoted (get m k)))) (keys m)))]
(str "(jolt-hash-map " (str/join " " pairs) ")")))
;; emit-quoted reconstructs both raw reader forms (from :quote) AND plain jolt
;; values (def/symbol :meta). Reader forms are walked via the jolt.host form-*
;; contract; the native-predicate branches below catch genuine jolt collection
@ -230,14 +232,16 @@
(str "(jolt-symbol/meta " (if sns (chez-str-lit sns) "#f") " " (chez-str-lit nm) " "
(emit-quoted m) ")")
(str "(jolt-symbol " (if sns (chez-str-lit sns) "#f") " " (chez-str-lit nm) ")")))
(form-set? form) (str "(jolt-hash-set " (str/join " " (map emit-quoted (form-set-items form))) ")")
;; sort items by emitted text: a set has no source order, and host-hash order
;; is not stable across Chez versions (jolt-8479).
(form-set? form) (str "(jolt-hash-set " (str/join " " (sort (map emit-quoted (form-set-items form)))) ")")
(form-list? form) (str "(jolt-list " (str/join " " (map emit-quoted (form-elements form))) ")")
(form-vec? form) (str "(jolt-vector " (str/join " " (map emit-quoted (form-vec-items form))) ")")
(form-map? form) (emit-quoted-map (form-map-pairs form))
;; plain jolt VALUES (metadata maps and anything nested in them)
(map? form) (emit-quoted-map-value form)
(vector? form) (str "(jolt-vector " (str/join " " (map emit-quoted form)) ")")
(set? form) (str "(jolt-hash-set " (str/join " " (map emit-quoted form)) ")")
(set? form) (str "(jolt-hash-set " (str/join " " (sort (map emit-quoted form))) ")")
(seq? form) (str "(jolt-list " (str/join " " (map emit-quoted form)) ")")
:else (throw (ex-info (str "emit-quoted: unsupported quoted form " (pr-str form)) {}))))

View file

@ -83,22 +83,26 @@
declarations from every dep's deps.edn. `base-dir` resolves :local/root and is
replaced by a dep's own root as the walk descends."
[deps base-dir]
;; queue grows by appending children at the tail; an index cursor walks it so
;; each dequeue is O(1) (was (subvec (vec queue) 1) per pop -> O(n^2)).
(loop [queue (mapv (fn [[c s]] [c s base-dir]) (seq deps))
i 0
seen #{}
roots []
natives []]
(if (empty? queue)
(if (>= i (count queue))
{:roots (distinct roots) :natives natives}
(let [[coord spec bd] (first queue)
queue (subvec (vec queue) 1)]
(let [[coord spec bd] (nth queue i)
i (inc i)]
(if (contains? seen coord)
(recur queue seen roots natives)
(recur queue i seen roots natives)
(let [root (coord-root coord spec bd)]
(if (nil? root)
(recur queue (conj seen coord) roots natives)
(recur queue i (conj seen coord) roots natives)
(let [edn (read-edn (str root "/deps.edn"))
child (mapv (fn [[c s]] [c s root]) (seq (:deps edn)))]
(recur (into queue child)
i
(conj seen coord)
(into roots (dep-source-roots root))
(into natives (:jolt/native edn)))))))))))

View file

@ -20,9 +20,6 @@
;; end emits the embedded var cell so `binding`'s thread-binding frame can key on it.
(defn the-var [ns name] {:op :the-var :ns ns :name name})
;; A runtime primitive (cons, +, get, apply, …) the back end maps to the host RT.
(defn rt [name] {:op :rt :name name})
;; A name that resolves only via the host's own environment (e.g. + or int?) —
;; the back end emits a host-appropriate reference.
(defn host-ref [name] {:op :host :name name})
@ -69,8 +66,6 @@
(defn quote-node [form] {:op :quote :form form})
(defn throw-node [expr] {:op :throw :expr expr})
(defn op [node] (:op node))
;; ---------------------------------------------------------------------------
;; Structural recursion over IR child nodes.
;;
@ -127,5 +122,5 @@
n (if (get node :catch-body) (assoc n :catch-body (f (get node :catch-body))) n)
n (if (get node :finally) (assoc n :finally (f (get node :finally))) n)]
n)
;; :const :local :var :host :host-static :the-var :rt :quote — no child nodes
;; :const :local :var :host :host-static :the-var :quote — no child nodes
:else node)))

View file

@ -1,463 +0,0 @@
(ns jolt.reader
"Reads Clojure source text into reader forms.
The lexing and parsing is portable Clojure; form construction and
string->number parsing delegate to the jolt.host contract (form-make-symbol/
char, form-char-from-name, form-scan-number). A Clojure source file can't write
a {:jolt/type :symbol} literal it would parse as a tagged reader form and
the concrete form representation belongs to the host. The analyzer uses the same
split. Once cross-compiled this runs on Chez to drive compile-from-source.
Positions are character indices; for ASCII source they coincide with byte
indices, and form values are identical either way the parity gate compares
values, not positions."
(:require [clojure.string :as str]
[jolt.host :refer [form-make-symbol form-make-char form-char-from-name
form-scan-number form-make-list form-make-vector
form-make-map form-sym-merge-meta form-make-set
form-make-tagged form-gensym-name
form-sym? form-sym-name form-sym-ns form-char?
form-list? form-vec? form-set? form-map?
form-elements form-vec-items form-set-items
form-map-pairs]]))
;; Source access by CHARACTER codepoint
;; (identical to byte access for ASCII). cp = codepoint at i; len = character count.
(defn- cp [s i] (int (nth s i)))
(defn- len [s] (count s))
(defn- whitespace? [c] (or (= c 32) (= c 9) (= c 10) (= c 13) (= c 44))) ; space tab nl cr ,
(defn- digit? [c] (and (>= c 48) (<= c 57)))
(defn- hex-digit? [c]
(or (digit? c) (and (>= c 65) (<= c 70)) (and (>= c 97) (<= c 102))))
(defn- symbol-start? [c]
(or (and (>= c 65) (<= c 90)) (and (>= c 97) (<= c 122))
(= c 42) (= c 43) (= c 33) (= c 95) (= c 45) (= c 63) (= c 46)
(= c 60) (= c 62) (= c 61) (= c 38) (= c 124) (= c 36) (= c 37) (= c 47)))
(defn- symbol-char? [c]
(or (symbol-start? c) (digit? c) (= c 35) (= c 39) (= c 58))) ; + # ' :
(defn- skip-whitespace [s pos]
(if (and (< pos (len s)) (whitespace? (cp s pos)))
(recur s (inc pos))
pos))
(defn- read-until-newline [s pos]
(if (or (>= pos (len s)) (= (cp s pos) 10)) pos (recur s (inc pos))))
;; --- symbols -----------------------------------------------------------------
(defn- read-symbol-name [s pos end]
(if (and (< end (len s)) (symbol-char? (cp s end))) (recur s pos (inc end)) end))
(defn- read-symbol* [s pos]
(let [end (read-symbol-name s pos pos)]
(when (= end pos)
(throw (ex-info (str "Unrecognized character: " (char (cp s pos))) {})))
(let [nm (subs s pos end)]
(cond
(= nm "nil") [nil end]
(= nm "true") [true end]
(= nm "false") [false end]
:else [(form-make-symbol nm) end]))))
;; --- keywords ----------------------------------------------------------------
(defn- read-keyword-name [s pos end]
(if (and (< end (len s)) (symbol-char? (cp s end))) (recur s pos (inc end)) end))
(defn- read-keyword* [s pos]
;; pos is at the first colon; ::foo is treated as :foo (no auto-resolution).
(let [start (if (and (< (inc pos) (len s)) (= (cp s (inc pos)) 58)) (+ pos 2) (inc pos))
end (read-keyword-name s start start)]
[(keyword (subs s start end)) end]))
;; --- strings -----------------------------------------------------------------
(defn- escape-char [c]
(cond (= c 110) "\n" (= c 116) "\t" (= c 114) "\r" (= c 92) "\\" (= c 34) "\""
:else (str (char c))))
(defn- read-string* [s pos]
;; pos at opening double-quote
(loop [p (inc pos) acc []]
(when (>= p (len s)) (throw (ex-info "Unterminated string" {})))
(let [c (cp s p)]
(cond
(= c 92) (let [np (inc p)]
(when (>= np (len s)) (throw (ex-info "Unterminated escape" {})))
(recur (+ p 2) (conj acc (escape-char (cp s np)))))
(= c 34) [(apply str acc) (inc p)]
:else (recur (inc p) (conj acc (str (char c))))))))
;; --- numbers -----------------------------------------------------------------
(defn- read-digits [s pos end]
(if (and (< end (len s)) (digit? (cp s end))) (recur s pos (inc end)) end))
(defn- read-hex-digits [s pos end]
(if (and (< end (len s)) (hex-digit? (cp s end))) (recur s pos (inc end)) end))
;; Value of an alphanumeric digit for radix parsing (0-9, a-z/A-Z = 10-35).
(defn- radix-digit-val [c]
(cond
(and (>= c 48) (<= c 57)) (- c 48)
(and (>= c 97) (<= c 122)) (+ 10 (- c 97))
(and (>= c 65) (<= c 90)) (+ 10 (- c 65))
:else nil))
(defn- read-alnum [s pos end]
(if (and (< end (len s)) (radix-digit-val (cp s end))) (recur s pos (inc end)) end))
(defn- read-exponent [s end]
;; if s[end] is e/E (optionally signed) followed by digits, return index past it
(if (and (< end (len s)) (let [c (cp s end)] (or (= c 101) (= c 69))))
(let [p (if (and (< (inc end) (len s)) (let [c (cp s (inc end))] (or (= c 43) (= c 45))))
(+ end 2) (inc end))
de (read-digits s p p)]
(if (> de p) de end))
end))
;; Jolt has no bignum/ratio: N (bigint) / M (bigdec) suffixes read as the plain
;; number, a ratio a/b reads as the double quotient, radixed ints by base.
(defn- read-number* [s pos]
(let [length (len s)
;; optional leading sign: - negates; + is a positive no-op (Clojure reads
;; +5 as 5). read-form only dispatches +digit/-digit, so the sign is real.
neg (and (< pos length) (= (cp s pos) 45))
plus (and (< pos length) (= (cp s pos) 43))
start (if (or neg plus) (inc pos) pos)
hex? (and (< (inc start) length) (= (cp s start) 48)
(let [c1 (cp s (inc start))] (or (= c1 120) (= c1 88))))] ; 0x / 0X
(if hex?
(let [hs (+ start 2) he (read-hex-digits s hs hs)]
(when (= he hs) (throw (ex-info "Expected hex digits" {})))
(let [he2 (if (and (< he length) (= (cp s he) 78)) (inc he) he) ; trailing N
val (form-scan-number (str "0x" (subs s hs he)))]
[(if neg (- val) val) he2]))
(let [iend (read-digits s start start)]
(when (= iend start) (throw (ex-info "Expected number" {})))
(cond
;; radix integer <base>r<digits>
(and (< iend length) (let [c (cp s iend)] (or (= c 114) (= c 82))))
(let [base (form-scan-number (subs s start iend))
ds (inc iend) de (read-alnum s ds ds)]
(when (= de ds) (throw (ex-info "Expected radix digits" {})))
(let [acc (reduce (fn [a i] (+ (* a base) (radix-digit-val (cp s i)))) 0 (range ds de))]
[(if neg (- acc) acc) de]))
;; ratio <int>/<int> (only when a digit follows the slash)
(and (< (inc iend) length) (= (cp s iend) 47) (digit? (cp s (inc iend))))
(let [ds (inc iend) de (read-digits s ds ds)
numr (form-scan-number (subs s start iend))
den (form-scan-number (subs s ds de))]
[(if neg (- (/ numr den)) (/ numr den)) de])
;; fractional and/or exponent, optional trailing N/M
:else
(let [frac-end (if (and (< iend length) (= (cp s iend) 46))
(let [fs (inc iend) fe (read-digits s fs fs)]
(when (= fe fs) (throw (ex-info "Expected digit after ." {})))
fe)
iend)
exp-end (read-exponent s frac-end)
val (form-scan-number (subs s start exp-end))
fin (if (and (< exp-end length) (let [c (cp s exp-end)] (or (= c 78) (= c 77))))
(inc exp-end) exp-end)]
[(if neg (- val) val) fin]))))))
;; --- characters --------------------------------------------------------------
(defn- read-char-name-end [s pos]
(if (and (< pos (len s)) (symbol-char? (cp s pos))) (recur s (inc pos)) pos))
(defn- read-char* [s pos]
(when (>= (inc pos) (len s)) (throw (ex-info "unexpected end of input after \\" {})))
(let [end (read-char-name-end s (inc pos))]
(if (= end (inc pos))
;; a non-symbol char right after \ is a one-character literal of itself
[(form-make-char (cp s (inc pos))) (+ pos 2)]
[(form-char-from-name (subs s (inc pos) end)) end])))
;; --- dispatcher --------------------------------------------------------------
;; read-form returns a CONTROL triple [kind payload pos]:
;; :form payload=the form a real datum
;; :skip payload=nil a comment (;) or #_ discard — produced nothing
;; :splice payload=items-vector #?@ — contributes 0+ items to the enclosing coll
;; Out-of-band control (rather than :jolt/skip / :jolt/splice sentinel
;; FORMS) keeps it collision-free and host-neutral — no tagged-struct to build or
;; recognize. Collection readers dispatch on kind; read-next-form skips :skip.
(declare read-form)
(defn- number-start? [s pos c]
(or (digit? c)
(and (= c 45) (< (inc pos) (len s)) (digit? (cp s (inc pos))))
(and (= c 43) (< (inc pos) (len s)) (digit? (cp s (inc pos))))))
;; Read items until `close`, dispatching control kinds. Returns [items-vec end].
(defn- read-delimited [s start-pos close errmsg]
(loop [pos start-pos items []]
(let [pos (skip-whitespace s pos)]
(when (>= pos (len s)) (throw (ex-info errmsg {})))
(if (= (cp s pos) close)
[items (inc pos)]
(let [[kind payload np] (read-form s pos)]
(case kind
:skip (recur np items)
:splice (recur np (into items payload))
:form (recur np (conj items payload))))))))
(defn- read-list* [s pos]
(let [[items end] (read-delimited s (inc pos) 41 "Unterminated list")] ; )
[:form (form-make-list items) end]))
(defn- read-vector* [s pos]
(let [[items end] (read-delimited s (inc pos) 93 "Unterminated vector")] ; ]
[:form (form-make-vector items) end]))
;; Map: pair up keys and values, skipping comments/#_ in either slot while keeping
;; the pending key (dropping both desyncs the pairing). A key/value is always a
;; single :form (or :skip) — splice in a map slot is not supported.
(defn- read-map* [s pos]
(loop [pos (inc pos) kvs []]
(let [pos (skip-whitespace s pos)]
(when (>= pos (len s)) (throw (ex-info "Unterminated map" {})))
(if (= (cp s pos) 125) ; }
[:form (form-make-map kvs) (inc pos)]
(let [[kk kp knp] (read-form s pos)]
(if (= kk :skip)
(recur knp kvs)
;; key in hand; read the value slot, skipping trivia but keeping the key
(let [[v vnp]
(loop [vp (skip-whitespace s knp)]
(when (>= vp (len s)) (throw (ex-info "Unterminated map" {})))
(let [[vk vp2 vnp2] (read-form s vp)]
(if (= vk :skip) (recur (skip-whitespace s vnp2)) [vp2 vnp2])))]
(recur vnp (conj (conj kvs kp) v)))))))))
;; Read the next REAL form (skip :skip), returning [form pos]. Used wherever a
;; single datum is needed (quote/meta/top level).
(defn- read-next-form [s pos]
(let [[kind payload np] (read-form s pos)]
(case kind
:skip (recur s np)
:form [payload np]
:splice (throw (ex-info "splice (#?@) not inside a collection" {})))))
;; syntax-quote of a self-evaluating literal collapses to the literal at read time
;; (so nested backticks over literals are inert). NOT symbols (they qualify) or
;; collections (they template).
(defn- self-evaluating-literal? [form]
(or (nil? form) (true? form) (false? form) (number? form)
(string? form) (keyword? form) (form-char? form)))
(defn- read-quote* [s newpos token-sym]
(let [[form finalpos] (read-next-form s newpos)]
(if (and (= "syntax-quote" (form-sym-name token-sym)) (self-evaluating-literal? form))
[:form form finalpos]
[:form (form-make-list [token-sym form]) finalpos])))
;; Normalize a metadata reader form: keyword -> {kw true}; symbol/string -> {:tag …}
;; (a symbol tag keeps its ns qualifier); else nil (a map-literal meta).
(defn- meta-form->map [meta-form]
(cond
(keyword? meta-form) {meta-form true}
(form-sym? meta-form) {:tag (if (form-sym-ns meta-form)
(str (form-sym-ns meta-form) "/" (form-sym-name meta-form))
(form-sym-name meta-form))}
(string? meta-form) {:tag meta-form}
:else nil))
(defn- read-meta* [s pos]
;; pos at ^
(let [[meta-form np] (read-next-form s (inc pos))
[form np2] (read-next-form s np)
m (meta-form->map meta-form)]
(if (and m (form-sym? form))
;; attach to the symbol itself (^Type x / ^:dynamic) — stays a bare symbol
[:form (form-sym-merge-meta form m) np2]
;; non-symbol target -> a runtime with-meta form (normalized map, or the
;; raw map-literal meta when m is nil)
[:form (form-make-list [(form-make-symbol "with-meta") form (if m m meta-form)]) np2])))
;; --- dispatch (#) ------------------------------------------------------------
;; Reader-conditional feature set (spec 02-reader). jolt's portable default; the
;; JOLT_FEATURES env override is a host concern wired later. :default always honored.
(def reader-features (atom #{:jolt :default}))
(defn set-reader-features! [features] (reset! reader-features (conj (set features) :default)))
(defn- read-set* [s pos]
;; pos at #, next char {
(let [[items end] (read-delimited s (+ pos 2) 125 "Unterminated set")] ; }
[:form (form-make-set items) end]))
(defn- read-var-quote* [s pos]
;; pos at #, next char '
(let [[form np] (read-next-form s (+ pos 2))]
[:form (form-make-list [(form-make-symbol "var") form]) np]))
(defn- read-regex* [s pos]
;; pos at #, next char "; read raw to the unescaped closing " (backslashes kept)
(loop [i (+ pos 2)]
(when (>= i (len s)) (throw (ex-info "Unterminated regex literal" {})))
(let [c (cp s i)]
(cond
(= c 92) (recur (+ i 2)) ; backslash escapes next char
(= c 34) [:form (form-make-tagged :regex (subs s (+ pos 2) i)) (inc i)]
:else (recur (inc i))))))
;; #?(…) / #?@(…): pick the first clause whose feature key is active (clause order,
;; like Clojure). #? -> :skip when the result is nil (e.g. a :cljs branch); #?@ ->
;; :splice the resolved items into the enclosing collection.
(defn- rc-resolve [clauses]
;; clauses: a jolt vector of [feature-kw form feature-kw form ...]
(loop [i 0]
(if (>= i (count clauses))
[false nil]
(if (contains? @reader-features (nth clauses i))
[true (nth clauses (inc i))]
(recur (+ i 2))))))
(defn- read-reader-conditional* [s pos]
;; pos at #, next char ? (optionally ?@)
(let [splice? (and (< (+ pos 2) (len s)) (= (cp s (+ pos 2)) 64)) ; @
form-start (if splice? (+ pos 3) (+ pos 2))
[form np] (read-next-form s form-start)]
(if (form-list? form)
(let [clauses (form-elements form)
[matched result] (rc-resolve clauses)]
(if splice?
(let [items (cond (not matched) []
(form-list? result) (vec (form-elements result))
(form-vec? result) (vec (form-vec-items result))
:else [result])]
[:splice items np])
(if (or (not matched) (nil? result)) [:skip nil np] [:form result np])))
(throw (ex-info "reader conditional body must be a list" {})))))
;; Symbolic values ##Inf ##-Inf ##NaN.
(defn- read-symbolic* [s pos]
(let [end (read-symbol-name s (+ pos 2) (+ pos 2))
nm (subs s (+ pos 2) end)]
(cond
(= nm "Inf") [:form ##Inf end]
(= nm "-Inf") [:form ##-Inf end]
(= nm "NaN") [:form ##NaN end]
:else (throw (ex-info (str "Invalid symbolic value: ##" nm) {})))))
(defn- read-tagged* [s pos]
;; unknown dispatch -> a tagged literal (#inst, #uuid, #foo). The tag includes
;; the leading # (read-symbol-name starts at #).
(let [end (read-symbol-name s pos pos)
tag (subs s pos end)
[form np] (read-next-form s end)]
[:form (form-make-tagged (keyword tag) form) np]))
(declare read-anon-fn*)
(defn- read-dispatch* [s pos]
;; pos at #
(when (>= (inc pos) (len s)) (throw (ex-info "Unexpected end after #" {})))
(let [c (cp s (inc pos))]
(cond
(= c 123) (read-set* s pos) ; #{
(= c 40) (read-anon-fn* s pos) ; #(
(= c 63) (read-reader-conditional* s pos) ; #?
(= c 95) (let [[_ _ np] (read-form s (+ pos 2))] [:skip nil np]) ; #_ discard
(= c 39) (read-var-quote* s pos) ; #'
(= c 94) (read-meta* s (inc pos)) ; #^ (deprecated, = ^)
(= c 34) (read-regex* s pos) ; #"
(= c 35) (read-symbolic* s pos) ; ##
:else (read-tagged* s pos))))
;; #(...) anonymous fn. Positional %-arg index: % and %1 => 1, %N => N, %& => the
;; rest param (:rest); anything else is not positional (nil). Fixed arity = max
;; index used (Clojure: #(do %2 %&) => [p1 p2 & rest], unused lower slots still
;; get a placeholder param).
(defn- pct-index [nm]
(cond
(= nm "%") 1
(= nm "%&") :rest
(and (> (count nm) 1) (= "%" (subs nm 0 1)))
(let [n (form-scan-number (subs nm 1))]
(if (and n (integer? n) (>= n 1)) n nil))
:else nil))
;; Pass 1: collect every %-index used anywhere in the form tree.
(defn- collect-pcts [form acc]
(cond
(form-sym? form) (let [i (pct-index (form-sym-name form))] (if i (conj acc i) acc))
(form-list? form) (reduce (fn [a x] (collect-pcts x a)) acc (form-elements form))
(form-vec? form) (reduce (fn [a x] (collect-pcts x a)) acc (form-vec-items form))
(form-set? form) (reduce (fn [a x] (collect-pcts x a)) acc (form-set-items form))
(form-map? form) (reduce (fn [a p] (collect-pcts (nth p 1) (collect-pcts (nth p 0) a)))
acc (form-map-pairs form))
:else acc))
;; Pass 2: replace each %-symbol with its slot's gensym (rebuilding collections).
(defn- replace-pct [form slot-syms rest-sym]
(cond
(form-sym? form) (let [idx (pct-index (form-sym-name form))]
(cond (= idx :rest) rest-sym
idx (get slot-syms idx)
:else form))
(form-list? form) (form-make-list (mapv #(replace-pct % slot-syms rest-sym) (form-elements form)))
(form-vec? form) (form-make-vector (mapv #(replace-pct % slot-syms rest-sym) (form-vec-items form)))
(form-set? form) (form-make-set (mapv #(replace-pct % slot-syms rest-sym) (form-set-items form)))
(form-map? form) (form-make-map
(vec (mapcat (fn [p] [(replace-pct (nth p 0) slot-syms rest-sym)
(replace-pct (nth p 1) slot-syms rest-sym)])
(form-map-pairs form))))
:else form))
(defn- gensym-param [] (form-make-symbol (str (form-gensym-name) "#")))
(defn- read-anon-fn* [s pos]
;; pos at #, next char (
(let [[form np] (read-next-form s (inc pos))
pcts (collect-pcts form [])
max-n (reduce (fn [m i] (if (and (number? i) (> i m)) i m)) 0 pcts)
has-rest (boolean (some #(= :rest %) pcts))
slot-syms (into {} (map (fn [i] [i (gensym-param)]) (range 1 (inc max-n))))
rest-sym (when has-rest (gensym-param))
replaced (replace-pct form slot-syms rest-sym)
arg-names (let [base (mapv #(get slot-syms %) (range 1 (inc max-n)))]
(if has-rest (conj base (form-make-symbol "&") rest-sym) base))]
[:form (form-make-list [(form-make-symbol "fn*") (form-make-vector arg-names) replaced]) np]))
(defn read-form [s pos]
(let [pos (skip-whitespace s pos)]
(if (>= pos (len s))
[:form nil pos]
(let [c (cp s pos)]
(cond
(= c 59) [:skip nil (read-until-newline s pos)] ; ; comment
(= c 34) (let [r (read-string* s pos)] [:form (nth r 0) (nth r 1)])
(= c 58) (let [r (read-keyword* s pos)] [:form (nth r 0) (nth r 1)])
(= c 92) (let [r (read-char* s pos)] [:form (nth r 0) (nth r 1)])
(= c 40) (read-list* s pos) ; (
(= c 91) (read-vector* s pos) ; [
(= c 123) (read-map* s pos) ; {
(= c 39) (read-quote* s (inc pos) (form-make-symbol "quote")) ; '
(= c 96) (read-quote* s (inc pos) (form-make-symbol "syntax-quote")) ; `
(= c 126) (if (and (< (inc pos) (len s)) (= (cp s (inc pos)) 64)) ; ~ / ~@
(read-quote* s (+ pos 2) (form-make-symbol "unquote-splicing"))
(read-quote* s (inc pos) (form-make-symbol "unquote")))
(= c 64) (read-quote* s (inc pos) (form-make-symbol "clojure.core/deref")) ; @
(= c 94) (read-meta* s pos) ; ^
(= c 41) (throw (ex-info "Unmatched delimiter: )" {}))
(= c 93) (throw (ex-info "Unmatched delimiter: ]" {}))
(= c 125) (throw (ex-info "Unmatched delimiter: }" {}))
(= c 35) (read-dispatch* s pos) ; #
(number-start? s pos c) (let [r (read-number* s pos)] [:form (nth r 0) (nth r 1)])
(symbol-start? c) (let [r (read-symbol* s pos)] [:form (nth r 0) (nth r 1)])
:else (throw (ex-info (str "read-form: unexpected char '" (char c) "' (" c ")") {})))))))
(defn read-one
"Read the first form of `s` (skipping leading trivia). Returns the form."
[s]
(first (read-next-form s 0)))
(defn read-all
"Read every top-level form of `s`, returning them in a vector (trivia skipped)."
[s]
(loop [pos 0 acc []]
(let [p (skip-whitespace s pos)]
(if (>= p (len s))
acc
(let [[kind payload np] (read-form s p)]
(case kind
:skip (recur np acc)
:splice (recur np (into acc payload))
:form (recur np (conj acc payload))))))))

View file

@ -55,12 +55,10 @@
PushbackReader, io/reader results) expose char-wise .read; a raw file
handle is read whole."
[reader]
(if (= :core/file (janet/type reader))
(janet.file/read reader :all)
(loop [acc (transient []) c (.read reader)]
(if (== -1 c)
(apply str (map char (persistent! acc)))
(recur (conj! acc c) (.read reader))))))
(recur (conj! acc c) (.read reader)))))
(defn read
"Reads one EDN object from reader (a PushbackReader or any jolt reader).

View file

@ -96,15 +96,6 @@
[s]
(str-trimr s))
(defn trim-newline
[s]
(var result s)
(while (or (= (subs result (dec (count result))) "\n")
(= (subs result (dec (count result))) "\r"))
(set result (subs result 0 (dec (count result)))))
result)
(defn escape
[s cmap]