Skip to content

Commit c260cea

Browse files
authored
CLJS-3299: port CLJ-2603 (#122)
Port CLJ-2603: Added support for trailing, conj-able element in map-destructuring support for seqs Faithfully port over all the relevant Java bits. * update --destructure-map helper * move key-test up for use by PAM.createAsIfByAssoc(ComplexPath) * port new PAM.createAsIfByAssoc - don't copy init, check for trailing & dupes * add PAM.createAsIfByComplexPath, ported from Java * add seq-to-map-for-destructuring * update docstrings * add tests
1 parent 72aa5c6 commit c260cea

3 files changed

Lines changed: 159 additions & 26 deletions

File tree

src/main/cljs/cljs/core.cljs

Lines changed: 103 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4005,8 +4005,14 @@ reduces them without incurring seq initialization"
40054005

40064006
;; CLJS-3200: used by destructure macro for maps to reduce amount of repeated code
40074007
;; placed here because it needs apply and hash-map (only declared at this point)
4008-
(defn --destructure-map [x]
4009-
(if (implements? ISeq x) (apply cljs.core/hash-map x) x))
4008+
(defn --destructure-map [gmap]
4009+
(if (implements? ISeq gmap)
4010+
(if (next gmap)
4011+
(.createAsIfByAssoc PersistentArrayMap (to-array gmap))
4012+
(if (seq gmap)
4013+
(first gmap)
4014+
(.-EMPTY PersistentArrayMap)))
4015+
gmap))
40104016

40114017
(defn vary-meta
40124018
"Returns an object of the same type and value as obj, with
@@ -7057,19 +7063,91 @@ reduces them without incurring seq initialization"
70577063
(let [cnt (/ (alength arr) 2)]
70587064
(PersistentArrayMap. nil cnt arr nil)))))
70597065

7066+
(defn key-test [key other]
7067+
(cond
7068+
(identical? key other) true
7069+
(keyword-identical? key other) true
7070+
:else (= key other)))
7071+
7072+
(defn- ^boolean pam-dupes? [arr]
7073+
(loop [i 0]
7074+
(if (< i (alength arr))
7075+
(let [dupe? (loop [j 0]
7076+
(if (< j i)
7077+
(or
7078+
(key-test (aget arr i) (aget arr j))
7079+
(recur (+ 2 j)))
7080+
false))]
7081+
(or dupe? (recur (+ 2 i))))
7082+
false)))
7083+
7084+
(defn- pam-new-size [arr]
7085+
(loop [i 0 n 0]
7086+
(if (< i (alength arr))
7087+
(let [dupe? (loop [j 0]
7088+
(if (< j i)
7089+
(or
7090+
(key-test (aget arr i) (aget arr j))
7091+
(recur (+ 2 j)))
7092+
false))]
7093+
(recur (+ 2 i) (if dupe? n (+ n 2))))
7094+
n)))
7095+
7096+
(defn- pam-grow-seed-array [seed trailing]
7097+
(let [seed-cnt (dec (alength seed))
7098+
extra-kvs (seq trailing)
7099+
ret (make-array (+ seed-cnt (* 2 (count extra-kvs))))
7100+
ret (array-copy seed 0 ret 0 seed-cnt)]
7101+
(loop [i seed-cnt extra-kvs extra-kvs]
7102+
(if extra-kvs
7103+
(let [kv (first extra-kvs)]
7104+
(aset ret i (-key kv))
7105+
(aset ret (inc i) (-val kv))
7106+
(recur (+ 2 seed-cnt) (next extra-kvs)))
7107+
ret))))
7108+
70607109
(set! (.-createAsIfByAssoc PersistentArrayMap)
7061-
(fn [arr]
7062-
(let [ret (array)]
7063-
(loop [i 0]
7064-
(when (< i (alength arr))
7065-
(let [k (aget arr i)
7066-
v (aget arr (inc i))
7067-
idx (array-index-of ret k)]
7068-
(if (== idx -1)
7069-
(doto ret (.push k) (.push v))
7070-
(aset ret (inc idx) v)))
7071-
(recur (+ i 2))))
7072-
(PersistentArrayMap. nil (/ (alength ret) 2) ret nil))))
7110+
(fn [init]
7111+
;; check trailing element
7112+
(let [len (alength init)
7113+
has-trailing? (== 1 (bit-and len 1))]
7114+
(if-not (or has-trailing? (pam-dupes? init))
7115+
(PersistentArrayMap. nil (/ len 2) init nil)
7116+
(.createAsIfByAssocComplexPath PersistentArrayMap init has-trailing?)))))
7117+
7118+
(set! (.-createAsIfByAssocComplexPath PersistentArrayMap)
7119+
(fn [init ^boolean has-trailing?]
7120+
(let [init (if has-trailing?
7121+
(pam-grow-seed-array init
7122+
;; into {} in case the final element is not a map but something conj-able
7123+
;; for parity with Clojure implementation of CLJ-2603
7124+
(into {} (aget init (dec (alength init)))))
7125+
init)
7126+
n (pam-new-size init)
7127+
len (alength init)]
7128+
(if (< n len)
7129+
(let [nodups (make-array n)]
7130+
(loop [i 0 m 0]
7131+
(if (< i len)
7132+
(let [dupe? (loop [j 0]
7133+
(if (< j m)
7134+
(or
7135+
(key-test (aget init i) (aget init j))
7136+
(recur (+ 2 j)))
7137+
false))]
7138+
(if-not dupe?
7139+
(let [j (loop [j (- len 2)]
7140+
(if (>= j i)
7141+
(if (key-test (aget init i) (aget init j))
7142+
j
7143+
(recur (- j 2)))
7144+
j))]
7145+
(aset nodups m (aget init i))
7146+
(aset nodups (inc m) (aget init (inc j)))
7147+
(recur (+ 2 i) (+ 2 m)))
7148+
(recur (+ 2 i) m)))))
7149+
(PersistentArrayMap. nil (/ (alength nodups) 2) nodups nil))
7150+
(PersistentArrayMap. nil (/ (alength init) 2) init nil)))))
70737151

70747152
(es6-iterable PersistentArrayMap)
70757153

@@ -7170,12 +7248,6 @@ reduces them without incurring seq initialization"
71707248

71717249
(declare create-inode-seq create-array-node-seq reset! create-node atom deref)
71727250

7173-
(defn key-test [key other]
7174-
(cond
7175-
(identical? key other) true
7176-
(keyword-identical? key other) true
7177-
:else (= key other)))
7178-
71797251
(defn- mask [hash shift]
71807252
(bit-and (bit-shift-right-zero-fill hash shift) 0x01f))
71817253

@@ -8947,7 +9019,17 @@ reduces them without incurring seq initialization"
89479019
(let [arr (if (and (instance? IndexedSeq keyvals) (zero? (.-i keyvals)))
89489020
(.-arr keyvals)
89499021
(into-array keyvals))]
8950-
(.createAsIfByAssoc PersistentArrayMap arr)))
9022+
(if (odd? (alength arr))
9023+
(throw (js/Error. (str "No value supplied for key: " (last arr))))
9024+
(.createAsIfByAssoc PersistentArrayMap arr))))
9025+
9026+
(defn seq-to-map-for-destructuring
9027+
"Builds a map from a seq as described in
9028+
https://clojure.org/reference/special_forms#keyword-arguments"
9029+
[s]
9030+
(if (next s)
9031+
(.createAsIfByAssoc PersistentArrayMap (to-array s))
9032+
(if (seq s) (first s) (.-EMPTY PersistentArrayMap))))
89519033

89529034
(defn obj-map
89539035
"keyval => key val

src/main/clojure/cljs/core.cljc

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,12 +267,14 @@
267267

268268
#?(:cljs
269269
(core/defmacro fn
270-
"params => positional-params* , or positional-params* & next-param
270+
"params => positional-params* , or positional-params* & rest-param
271271
positional-param => binding-form
272-
next-param => binding-form
273-
name => symbol
272+
rest-param => binding-form
273+
binding-form => name, or destructuring-form
274274
275-
Defines a function"
275+
Defines a function
276+
277+
See https://clojure.org/reference/special_forms#fn for more information"
276278
{:forms '[(fn name? [params*] exprs*) (fn name? ([params*] exprs*) +)]}
277279
[& sigs]
278280
(core/let [name (if (core/symbol? (first sigs)) (first sigs) nil)
@@ -769,10 +771,15 @@
769771

770772
(core/defmacro let
771773
"binding => binding-form init-expr
774+
binding-form => name, or destructuring-form
775+
destructuring-form => map-destructure-form, or seq-destructure-form
772776
773777
Evaluates the exprs in a lexical context in which the symbols in
774778
the binding-forms are bound to their respective init-exprs or parts
775-
therein."
779+
therein.
780+
781+
See https://clojure.org/reference/special_forms#binding-forms for
782+
more information about destructuring."
776783
[bindings & body]
777784
(assert-args let
778785
(vector? bindings) "a vector for its binding"

src/test/cljs/cljs/destructuring_test.cljs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,47 @@
185185
(is (= 1 (f 1)))
186186
(is (= 1 (f 1 2))))
187187
(let []))
188+
189+
(deftest test-pam-dupes?
190+
(is (false? (#'pam-dupes? #js [:a 1 :b 2 :c 3])))
191+
(is (true? (#'pam-dupes? #js [:a 1 :b 2 :a 3]))))
192+
193+
(deftest test-pam-new-size
194+
(is (== 6 (#'pam-new-size #js [:a 1 :b 2 :c 3])))
195+
(is (== 4 (#'pam-new-size #js [:a 1 :b 2 :a 3]))))
196+
197+
(deftest singleton-map-in-destructure-context
198+
(let [sample-map {:a 1 :b 2}
199+
{:keys [a] :as m1} (list sample-map)]
200+
(is (= m1 sample-map))
201+
(is (= a 1))))
202+
203+
(deftest trailing-map-destructuring
204+
(let [add (fn [& {:keys [a b]}] (+ a b))
205+
addn (fn [n & {:keys [a b]}] (+ n a b))]
206+
(testing "that kwargs are applied properly given a map in place of the key/val pairs"
207+
(is (= 3 (add :a 1 :b 2)))
208+
(is (= 3 (add {:a 1 :b 2})))
209+
(is (= 13 (addn 10 :a 1 :b 2)))
210+
(is (= 13 (addn 10 {:a 1 :b 2})))
211+
(is (= 103 ((partial addn 100) :a 1 {:b 2})))
212+
(is (= 103 ((partial addn 100 :a 1) {:b 2})))
213+
(is (= 107 ((partial addn 100 :a 1) {:a 5 :b 2}))))
214+
(testing "built maps"
215+
(let [{:as m1} (list :a 1 :b 2)
216+
{:as m2} (list :a 1 :b 2 {:c 3})
217+
{:as m3} (list :a 1 :b 2 {:a 0})
218+
{:keys [a4] :as m4} (list nil)]
219+
(= m1 {:a 1 :b 2})
220+
(= m2 {:a 1 :b 2 :c 3})
221+
(= m3 {:a 0 :b 2})
222+
(= m1 (seq-to-map-for-destructuring (list :a 1 :b 2)))
223+
(= m2 (seq-to-map-for-destructuring (list :a 1 :b 2 {:c 3})))
224+
(= m3 (seq-to-map-for-destructuring (list :a 1 :b 2 {:a 0})))
225+
(= a4 nil)))))
226+
227+
(comment
228+
229+
(cljs.test/run-tests)
230+
231+
)

0 commit comments

Comments
 (0)