Skip to content

Commit d3891c5

Browse files
authored
Support destructuring namespaced symbols (#1009)
Fixes #836
1 parent b7a287d commit d3891c5

File tree

4 files changed

+133
-28
lines changed

4 files changed

+133
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* Added `basilisp.pprint/print-table` function (#983)
1313
* Added `basilisp.core/read-seq` function (#986, #999)
1414
* Added various compiler arguments to CLI commands (#989)
15+
* Added support for destructuring namespaced symbols using the `:ns/syms` syntax and non-keyword values in standard associative destructuring forms (#836)
1516

1617
### Changed
1718
* Improved on the nREPL server exception messages by matching that of the REPL user-friendly format (#968)

docs/concepts.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -590,17 +590,17 @@ There exists a corresponding construct for the symbol and string key cases as we
590590

591591
The keys for the ``:strs`` construct must be convertible to valid Basilisp symbols.
592592

593-
It is possible to bind namespaced keys directly using either namespaced individual keys or a namespaced version of ``:keys`` as ``:ns/keys``.
593+
It is possible to bind namespaced keys and symbols directly using either namespaced individual values or a namespaced version of ``:keys`` (such as ``:ns/keys``) or ``:syms`` (``:ns/syms``).
594594
Values will be bound to the symbol by their *name* only (as by :lpy:fn:`name`) -- the namespace is only used for lookup in the associative data structure.
595595

596596
.. code-block::
597597
598-
(let [{a :a b :a/b :c/keys [c d]} {:a "a"
599-
:b "b"
600-
:a/a "aa"
601-
:a/b "bb"
602-
:c/c "cc"
603-
:c/d "dd"}]
598+
(let [{a :a b 'a/b :c/keys [c] :c/syms [d]} {:a "a"
599+
:b "b"
600+
:a/a "aa"
601+
'a/b "bb"
602+
:c/c "cc"
603+
'c/d "dd"}]
604604
[a b c d])
605605
;;=> ["a" "bb" "cc" "dd"]
606606

src/basilisp/core.lpy

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5578,6 +5578,12 @@
55785578
(filter keyword?)
55795579
(filter #(= "keys" (name %))))
55805580

5581+
;; Symbols may also be destructured from namespaced values as
5582+
;; keywords can.
5583+
sym-keys (->> (keys arg)
5584+
(filter keyword?)
5585+
(filter #(= "syms" (name %))))
5586+
55815587
;; Keyword, string, and symbol keys may all be destructured from
55825588
;; associative types.
55835589
kws (mapcat (fn [kw]
@@ -5587,14 +5593,19 @@
55875593
kw-ns (map #(symbol kw-ns (name %))))))
55885594
kw-keys)
55895595
strs (:strs arg)
5590-
syms (:syms arg)
5596+
syms (mapcat (fn [sym]
5597+
(let [sym-ns (namespace sym)
5598+
syms (get arg sym)]
5599+
(cond->> syms
5600+
sym-ns (map #(symbol sym-ns (name %))))))
5601+
sym-keys)
55915602

55925603
;; Fetch all the remaining keys in the map which do not
55935604
;; correspond to special functionality.
55945605
;; Destructuring forms may be nested arbitrarily, so generate the
55955606
;; definitions for any nested destructured forms as well.
55965607
children (->> [:as :or :strs :syms]
5597-
(concat kw-keys)
5608+
(concat kw-keys sym-keys)
55985609
(apply dissoc arg)
55995610
(map (fn [arg]
56005611
;; The val in this case is actually the key from
@@ -5640,10 +5651,8 @@
56405651

56415652
(defmethod destructure-def :default
56425653
[arg]
5643-
(throw
5644-
(ex-info "Invalid destructuring argument type"
5645-
{:type (python/type arg)
5646-
:arg arg})))
5654+
{:name arg
5655+
:type :other})
56475656

56485657
(defmulti ^:private destructure-binding
56495658
(fn [ddef]
@@ -5700,11 +5709,12 @@
57005709
[arg `(get ~fn-arg ~k)])))
57015710

57025711
sym-binding (fn [arg]
5703-
(let [k (symbol (name arg))
5704-
or-binding (get ors arg)]
5712+
(let [sym-name (name arg)
5713+
sym (symbol sym-name)
5714+
or-binding (get ors sym)]
57055715
(if or-binding
5706-
[arg `(or (get ~fn-arg (quote ~k)) ~or-binding)]
5707-
[arg `(get ~fn-arg (quote ~k))])))
5716+
[sym `(or (get ~fn-arg (quote ~arg)) ~or-binding)]
5717+
[sym `(get ~fn-arg (quote ~arg))])))
57085718

57095719
named-binding (fn [arg]
57105720
(let [binding (get-in arg [:binding :name])

tests/basilisp/test_core_macros.lpy

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -825,16 +825,39 @@
825825
(is (= [:c m] (f m)))
826826
(is (= ["aa" m1] (f m1)))))
827827

828+
(testing "destructuring namespaced symbols"
829+
(let [m {'a "a"
830+
'b "b"
831+
'a/a "aa"
832+
'a/b "bb"
833+
'c/c "cc"
834+
'c/d "dd"}
835+
836+
f (fn [{:a/syms [a] :syms [b]}]
837+
[a b])
838+
g (fn [{:syms [a b] :c/syms [c d]}]
839+
[a b c d])]
840+
(is (= ["aa" "b"] (f m)))
841+
(is (= ["a" "b" "cc" "dd"] (g m))))
842+
843+
(let [m {'a "a"}
844+
m1 {'a/a "aa" 'a "a"}
845+
846+
f (fn [{:a/syms [a] :as m :or {a :c}}]
847+
[a m])]
848+
(is (= [:c m] (f m)))
849+
(is (= ["aa" m1] (f m1)))))
850+
828851
(testing "destructuring individual keys"
829-
(let [m {:a "a" :b "b"}
852+
(let [m {:a "a" :b "b" 3 "three" "key" "string-key"}
830853

831-
f (fn [{a :a}]
832-
[a])
854+
f (fn [{a :a three 3 s "key"}]
855+
[a three s])
833856
g (fn [{a :a :as m}]
834857
[a m])
835858
h (fn [{a :a b :b :as m}]
836859
[a b m])]
837-
(is (= ["a"] (f m)))
860+
(is (= ["a" "three" "string-key"] (f m)))
838861
(is (= ["a" m] (g m)))
839862
(is (= ["a" "b" m] (h m))))
840863

@@ -1042,9 +1065,11 @@
10421065
(is (= {:a 1 :b "b" 'a :a} m))))
10431066

10441067
(testing "destructuring individual keys"
1045-
(let [{a :a b :b} {:a "a" :b "b"}]
1046-
(is (= "a" a))
1047-
(is (= "b" b)))
1068+
(let [{a :a b :b three 3 s "key"} {:a "a"
1069+
:b "b"
1070+
3 "three"
1071+
"key" "string-key"}]
1072+
(is (= [a b three s] ["a" "b" "three" "string-key"])))
10481073

10491074
(let [{a :a b :b :as m} {:a "a" :b "b"}]
10501075
(is (= "a" a))
@@ -1089,6 +1114,36 @@
10891114
(is (= "ee" c))
10901115
(is (= "dd" d))))
10911116

1117+
(testing "destructuring namespaced symbols"
1118+
(let [{:a/syms [a b] old-a 'a old-b 'b} {'a "a"
1119+
'b "b"
1120+
'a/a "aa"
1121+
'a/b "bb"
1122+
'c/c "cc"
1123+
'c/d "dd"}]
1124+
(is (= "aa" a))
1125+
(is (= "bb" b))
1126+
(is (= "a" old-a))
1127+
(is (= "b" old-b)))
1128+
1129+
(let [{:syms [a b] :c/syms [c d]} {'a "a"
1130+
'b "b"
1131+
'a/a "aa"
1132+
'a/b "bb"
1133+
'c/c "cc"
1134+
'c/d "dd"}]
1135+
(is (= "a" a))
1136+
(is (= "b" b))
1137+
(is (= "cc" c))
1138+
(is (= "dd" d)))
1139+
1140+
(let [{:syms [a b] :c/syms [c d] :or {c "ee"}}
1141+
{'a "a" 'b "b" 'a/a "aa" 'a/b "bb" 'c/d "dd"}]
1142+
(is (= "a" a))
1143+
(is (= "b" b))
1144+
(is (= "ee" c))
1145+
(is (= "dd" d))))
1146+
10921147
(testing "nested destructuring"
10931148
(let [{{:keys [a b] :as nested} :nested :as m} {:nested {:a "aa" :b "bb"} :a "a" :b "b"}]
10941149
(is (= "aa" a))
@@ -1276,15 +1331,36 @@
12761331
(is (= [:c m] (f m)))
12771332
(is (= ["aa" m1] (f m1))))))
12781333

1334+
(testing "destructuring namespaced symbols"
1335+
(let [m {'a "a"
1336+
'b "b"
1337+
'a/a "aa"
1338+
'a/b "bb"
1339+
'c/c "cc"
1340+
'c/d "dd"}]
1341+
(letfn [(f [{:a/syms [a b] old-a 'a old-b 'b}]
1342+
[old-a old-b a b])
1343+
(g [{:syms [a b] :c/syms [c d]}]
1344+
[a b c d])]
1345+
(is (= ["a" "b" "aa" "bb"] (f m)))
1346+
(is (= ["a" "b" "cc" "dd"] (g m)))))
1347+
1348+
(let [m {'a "a"}
1349+
m1 {'a/a "aa" 'a "a"}]
1350+
(letfn [(f [{:a/syms [a] :as m :or {a :c}}]
1351+
[a m])]
1352+
(is (= [:c m] (f m)))
1353+
(is (= ["aa" m1] (f m1))))))
1354+
12791355
(testing "destructuring individual keys"
1280-
(let [m {:a "a" :b "b"}]
1281-
(letfn [(f [{a :a}]
1282-
[a])
1356+
(let [m {:a "a" :b "b" 3 "three" "key" "string-key"}]
1357+
(letfn [(f [{a :a three 3 s "key"}]
1358+
[a three s])
12831359
(g [{a :a :as m}]
12841360
[a m])
12851361
(h [{a :a b :b :as m}]
12861362
[a b m])]
1287-
(is (= ["a"] (f m)))
1363+
(is (= ["a" "three" "string-key"] (f m)))
12881364
(is (= ["a" m] (g m)))
12891365
(is (= ["a" "b" m] (h m)))))
12901366

@@ -1507,6 +1583,24 @@
15071583
(conj accum a b))
15081584
[coll (apply str accum)])))))
15091585

1586+
(testing "destructuring namespaced syms"
1587+
(is (= [[{'a/a "A" 'a "a" 'b "b"}
1588+
{'a/a "C" 'a "c" 'b "d"}
1589+
{'a "e" 'b "e"}]
1590+
"AbCdZe"]
1591+
(loop [items [{'a/a "A" 'a "a" 'b "b"}
1592+
{'a/a "C" 'a "c" 'b "d"}
1593+
{'a "e" 'b "e"}]
1594+
{:a/syms [a] b 'b :as m :or {a "Z"}} (first items)
1595+
coll []
1596+
accum []]
1597+
(if m
1598+
(recur (rest items)
1599+
(first (rest items))
1600+
(conj coll m)
1601+
(conj accum a b))
1602+
[coll (apply str accum)])))))
1603+
15101604
(testing "nested destructuring"
15111605
(is (= [[{:nested {:a "A"} :a "a" :b "b"}
15121606
{:nested {:a "C"} :a "c" :b "d"}

0 commit comments

Comments
 (0)