diff --git a/src/jolt/phm.janet b/src/jolt/phm.janet index f86c3b0..9ba69ee 100644 --- a/src/jolt/phm.janet +++ b/src/jolt/phm.janet @@ -39,25 +39,26 @@ (defn- hash-idx [m k] (if (nil? k) 0 (mod (hash (ck k)) (length (m :buckets))))) -(defn- phm-bucket-find [bucket k] - (var i 0) (var n (length bucket)) (var found nil) +# Index of key k in a bucket (entries are stored stride-2: k v k v ...), or nil. +# The single scan all the bucket ops share — keeping it one place stops the +# stride logic drifting between them. +(defn- bucket-index-of [bucket k] + (var i 0) (def n (length bucket)) (var found nil) (while (< i n) - (if (key= k (in bucket i)) (do (set found (in bucket (+ i 1))) (break))) + (if (key= k (in bucket i)) (do (set found i) (break))) (+= i 2)) found) +(defn- phm-bucket-find [bucket k] + (let [i (bucket-index-of bucket k)] + (if i (in bucket (+ i 1)) nil))) + (defn phm-bucket-contains? [bucket k] - (var i 0) (var n (length bucket)) (var found false) - (while (< i n) - (if (key= k (in bucket i)) (do (set found true) (break))) - (+= i 2)) - found) + (not (nil? (bucket-index-of bucket k)))) (defn- phm-bucket-assoc [bucket k v] - (var i 0) (var n (length bucket)) (var found-i nil) - (while (< i n) - (if (key= k (in bucket i)) (do (set found-i i) (break))) - (+= i 2)) + (def n (length bucket)) + (def found-i (bucket-index-of bucket k)) (if (not (nil? found-i)) (let [nb @[]] (var j 0) (while (< j n) (array/push nb (if (= j (+ found-i 1)) v (in bucket j))) (++ j)) nb) @@ -66,10 +67,8 @@ (array/push nb k) (array/push nb v) nb))) (defn- phm-bucket-dissoc [bucket k] - (var i 0) (var n (length bucket)) (var found-i nil) - (while (< i n) - (if (key= k (in bucket i)) (do (set found-i i) (break))) - (+= i 2)) + (def n (length bucket)) + (def found-i (bucket-index-of bucket k)) (if (nil? found-i) bucket (if (= n 2) nil (let [nb @[]] (var j 0) @@ -82,12 +81,8 @@ # Single pass with a presence flag (not nil-of-value): a key mapped to nil # is still present, so return nil (not the default) when it exists. (if bucket - (do - (var i 0) (var n (length bucket)) (var result default) - (while (< i n) - (if (key= k (in bucket i)) (do (set result (in bucket (+ i 1))) (break))) - (+= i 2)) - result) + (let [i (bucket-index-of bucket k)] + (if i (in bucket (+ i 1)) default)) default))) # Rehash every entry of `buckets` into a fresh array of `nb` buckets. diff --git a/src/jolt/reader.janet b/src/jolt/reader.janet index 1f1ff44..46883be 100644 --- a/src/jolt/reader.janet +++ b/src/jolt/reader.janet @@ -298,39 +298,35 @@ (+ exp-end 1) exp-end)] [(if neg (- val) val) fin])))))) -(defn read-list [s pos] - # pos is at opening paren - (defn read-list-items [s pos items] +# Read forms until `close` (a byte), returning [items end-pos]. Shared by the +# list/vector/set readers: skip #_ discards and splice #?@ items uniformly, so the +# skip/splice handling can't drift between them (it did once). The map reader +# keeps its own loop — its key/value pairing needs a different value-slot scan. +(defn read-delimited [s start-pos close err] + (defn read-items [s pos items] (let [pos (skip-whitespace s pos)] (if (>= pos (length s)) - (reader-error "Unterminated list" pos)) - (if (= (s pos) 41) # ) + (reader-error err pos)) + (if (= (s pos) close) [items (+ pos 1)] (let [[form new-pos] (read-form s pos)] # skip #_ discarded forms (if (and (struct? form) (= :jolt/skip (form :jolt/type))) - (read-list-items s new-pos items) - # splice #?@ items into the list + (read-items s new-pos items) + # splice #?@ items into the collection (if (and (struct? form) (= :jolt/splice (form :jolt/type))) - (read-list-items s new-pos (array/concat items (form :items))) - (read-list-items s new-pos (array/push items form)))))))) - (read-list-items s (+ pos 1) @[])) + (read-items s new-pos (array/concat items (form :items))) + (read-items s new-pos (array/push items form)))))))) + (read-items s start-pos @[])) + +(defn read-list [s pos] + # pos is at opening paren + (read-delimited s (+ pos 1) 41 "Unterminated list")) # ) (defn read-vector [s pos] # pos is at opening bracket - (defn read-vec-items [s pos items] - (let [pos (skip-whitespace s pos)] - (if (>= pos (length s)) - (reader-error "Unterminated vector" pos)) - (if (= (s pos) 93) # ] - [(tuple/slice (tuple ;items)) (+ pos 1)] - (let [[form new-pos] (read-form s pos)] - (if (and (struct? form) (= :jolt/skip (form :jolt/type))) - (read-vec-items s new-pos items) - (if (and (struct? form) (= :jolt/splice (form :jolt/type))) - (read-vec-items s new-pos (array/concat items (form :items))) - (read-vec-items s new-pos (array/push items form)))))))) - (read-vec-items s (+ pos 1) @[])) + (let [[items end] (read-delimited s (+ pos 1) 93 "Unterminated vector")] # ] + [(tuple/slice (tuple ;items)) end])) # A map-literal form. Janet structs drop nil keys/values, so when a key or value # is nil (e.g. {:a nil}) build a phm — it preserves nil, matching Clojure. The @@ -400,19 +396,8 @@ (defn read-set [s pos] # pos is at #, next char is { - (defn read-set-items [s pos items] - (let [pos (skip-whitespace s pos)] - (if (>= pos (length s)) - (reader-error "Unterminated set" pos)) - (if (= (s pos) 125) # } - [{:jolt/type :jolt/set :value (tuple/slice (tuple ;items))} (+ pos 1)] - (let [[form new-pos] (read-form s pos)] - (if (and (struct? form) (= :jolt/skip (form :jolt/type))) - (read-set-items s new-pos items) - (if (and (struct? form) (= :jolt/splice (form :jolt/type))) - (read-set-items s new-pos (array/concat items (form :items))) - (read-set-items s new-pos (array/push items form)))))))) - (read-set-items s (+ pos 2) @[])) + (let [[items end] (read-delimited s (+ pos 2) 125 "Unterminated set")] # } + [{:jolt/type :jolt/set :value (tuple/slice (tuple ;items))} end])) (defn read-char-name-end [s pos] (if (and (< pos (length s)) (symbol-char? (s pos)))