Skip to content

Commit fa5fad0

Browse files
committed
docs: on parsing of invalid code, sexpr-able?
There is, rightly so, confusion on how sexpr-able? behaves. There was also a lack of detail around what technically invalid Clojure code rewrite-clj parses. I've updated user guide and docstrings to clarify. Closes #150, closes #151
1 parent bb1ef0a commit fa5fad0

File tree

6 files changed

+186
-27
lines changed

6 files changed

+186
-27
lines changed

doc/01-user-guide.adoc

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,85 @@ image::map-nodes.png[map nodes]
698698

699699
This is convenient when navigating through the source, but when we want to logically treat any map as a map the difference is admittedly bit awkward.
700700

701+
== Parsing Peculiarities
702+
703+
Rewrite-clj can, in some specific cases, parse technically invalid Clojure.
704+
Some folks have come to rely on this over the years, so these are behaviours we will preserve.
705+
706+
[[unbalanced-maps]]
707+
=== Unbalanced Maps
708+
An unbalanced map is one where there is a key with no value.
709+
710+
Rewrite-clj can parse and emit unbalanced maps:
711+
[source,clojure]
712+
----
713+
(require '[rewrite-clj.zip :as z])
714+
715+
(-> "{:a 1 :b 2 :c}"
716+
z/of-string
717+
z/root-string)
718+
;; => "{:a 1 :b 2 :c}"
719+
----
720+
721+
An attempt to convert an unbalanced map to a Clojure form will throw:
722+
//#:test-doc-blocks {:reader-cond :clj}
723+
[source,clojure]
724+
----
725+
(try
726+
(-> "{:a 1 :b 2 :c}"
727+
z/of-string
728+
z/sexpr)
729+
(catch Throwable e
730+
(.getMessage e)))
731+
;; => "No value supplied for key: :c"
732+
----
733+
734+
NOTE: `sexpr-able?` considers the current node element type only and will return `true` for all maps, balanced or not.
735+
736+
[[maps-with-duplicate-keys]]
737+
=== Maps with Duplicate keys
738+
Rewrite-clj can parse and emit maps with duplicate keys:
739+
740+
[source,clojure]
741+
----
742+
(-> "{:a 1 :b 2 :a 3 :a 4 :a 5 :a 6}"
743+
z/of-string
744+
z/root-string)
745+
;; => "{:a 1 :b 2 :a 3 :a 4 :a 5 :a 6}"
746+
----
747+
748+
But when converting to a Clojure form, duplicate keys are not valid in a map, so only the last key/value pair for duplicate keys will be included:
749+
[source,clojure]
750+
----
751+
(-> "{:a 1 :b 2 :a 3 :a 4 :a 5 :a 6}"
752+
z/of-string
753+
z/sexpr)
754+
;; => {:b 2, :a 6}
755+
----
756+
757+
[[sets-with-duplicate-values]]
758+
=== Sets with Duplicate values
759+
760+
Rewrite-clj can parse and emit sets with duplicate values:
761+
762+
[source,clojure]
763+
----
764+
(-> "#{:a :b :a :a :a}"
765+
z/of-string
766+
z/root-string)
767+
;; => "#{:a :b :a :a :a}"
768+
----
769+
770+
But when converting to a Clojure form, duplicate values in a set are not valid Clojure, so the duplicates are omitted:
771+
772+
[source,clojure]
773+
----
774+
(-> "#{:a :b :a :a :a}"
775+
z/of-string
776+
z/sexpr)
777+
;; => #{:b :a}
778+
----
779+
701780
[#sexpr-nuances]
702781
== Sexpr Nuances
703782

@@ -754,15 +833,24 @@ The whitespace that a rewrite-clj so carefully preserves is lost when converting
754833
; {:b 2, :a 1}
755834
----
756835

757-
=== Not all Source is Sexpr-able
836+
[[not-all-clojure-is-sexpr-able]]
837+
=== Not all Clojure Elements are Sexpr-able
758838

759-
Some source code elements are not sexpr-able:
839+
Some source code element types are not sexpr-able:
760840

761841
* Reader ignore/discard `#_` (also known as "uneval" in rewrite-clj)
762842
* Comments
763843
* Clojure whitespace (which includes commas)
764844

765-
Both the zip and node APIs include `sexpr-able?` to check if sexpr is supported for a node.
845+
Both the zip and node APIs include `sexpr-able?` to check if sexpr is supported for the current node element type.
846+
847+
[NOTE]
848+
====
849+
`sexpr-able?` only looks at the current node element type. This means that `sexpr` will still throw when:
850+
851+
1. called on a node with an element type that is `sepxr-able?` but, for whatever reason, has a child node that fails to `sexpr`, see link:#unbalanced-maps[unbalanced maps].
852+
2. called directly on an link:#unbalanced-maps[unbalanced maps].
853+
====
766854

767855
[source, clojure]
768856
----
@@ -823,6 +911,19 @@ Both the zip and node APIs include `sexpr-able?` to check if sexpr is supported
823911
----
824912
<1> Notice the use of `next*` to include normally skipped nodes.
825913

914+
Remember that child nodes with element types that are not `sexpr-able?` are skipped for `sexpr`:
915+
916+
[source,clojure]
917+
----
918+
(-> (str "[1 #_:child-discard-will-be-skipped\n"
919+
" ;; comment will be skipped\n"
920+
" ,,, ,,, ,,, \n"
921+
" 2]")
922+
z/of-string
923+
z/sexpr)
924+
;; => [1 2]
925+
----
926+
826927
=== Differences in Clojure Platforms
827928

828929
Clojure and ClojureScript have differences.

src/rewrite_clj/node.cljc

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@
165165

166166
;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.node.protocols
167167
(defn sexpr-able?
168-
"Return true if [[sexpr]] is supported for `node`."
168+
"Return true if [[sexpr]] is supported for `node`'s element type.
169+
170+
See [related docs in user guide](/doc/01-user-guide.adoc#not-all-clojure-is-sexpr-able)"
169171
[node] (rewrite-clj.node.protocols/sexpr-able? node))
170172

171173
;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.node.protocols
@@ -551,7 +553,7 @@
551553
;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.node.seq
552554
(defn list-node
553555
"Create a node representing a list with `children`.
554-
556+
555557
```Clojure
556558
(require '[rewrite-clj.node :as n])
557559
@@ -569,7 +571,7 @@
569571
(defn map-node
570572
"Create a node representing a map with `children`.
571573
```Clojure
572-
(require '[rewrite-clj.node :as n])
574+
(require '[rewrite-clj.node :as n])
573575
574576
(-> (n/map-node [(n/keyword-node :a)
575577
(n/spaces 1)
@@ -579,41 +581,66 @@
579581
(n/spaces 1)
580582
(n/token-node 2)])
581583
(n/string))
582-
;; => \"{:a 1 :b 2}\"
584+
;; => \"{:a 1 :b 2}\"
583585
```
584586
585-
Note that rewrite-clj allows unbalanced maps:
587+
Note that rewrite-clj allows the, technically illegal, unbalanced map:
586588
```Clojure
587589
(-> (n/map-node [(n/keyword-node :a)])
588590
(n/string))
589591
;; => \"{:a}\"
590592
```
591-
Note also that [[sexpr]] will fail on an unbalanced map."
593+
See [docs on unbalanced maps](/doc/01-user-guide.adoc#unbalanced-maps).
594+
595+
Rewrite-clj also allows the, also technically illegal, map with duplicate keys:
596+
```Clojure
597+
(-> (n/map-node [(n/keyword-node :a)
598+
(n/spaces 1)
599+
(n/token-node 1)
600+
(n/spaces 1)
601+
(n/keyword-node :a)
602+
(n/spaces 1)
603+
(n/token-node 2)])
604+
(n/string))
605+
;; => \"{:a 1 :a 2}\"
606+
```
607+
See [docs on maps with duplicate keys](/doc/01-user-guide.adoc#maps-with-duplicate-keys)."
592608
[children] (rewrite-clj.node.seq/map-node children))
593609

594610
;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.node.seq
595611
(defn set-node
596612
"Create a node representing a set with `children`.
597-
613+
598614
```Clojure
599-
(require '[rewrite-clj.node :as n])
615+
(require '[rewrite-clj.node :as n])
600616
601617
(-> (n/set-node [(n/token-node 1)
602618
(n/spaces 1)
603619
(n/token-node 2)
604620
(n/spaces 1)
605621
(n/token-node 3)])
606-
n/string)
622+
n/string)
607623
;; => \"#{1 2 3}\"
608-
```"
624+
```
625+
626+
Note that rewrite-clj allows the, technically illegal, set with duplicate values:
627+
```Clojure
628+
(-> (n/set-node [(n/token-node 1)
629+
(n/spaces 1)
630+
(n/token-node 1)])
631+
(n/string))
632+
;; => \"#{1 1}\"
633+
```
634+
635+
See [docs on sets with duplicate values](/doc/01-user-guide.adoc#sets-with-duplicate-values)."
609636
[children] (rewrite-clj.node.seq/set-node children))
610637

611638
;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.node.seq
612639
(defn vector-node
613640
"Create a node representing a vector with `children`.
614641
615642
```Clojure
616-
(require '[rewrite-clj.node :as n])
643+
(require '[rewrite-clj.node :as n])
617644
618645
(-> (n/vector-node [(n/token-node 1)
619646
(n/spaces 1)

src/rewrite_clj/node/protocols.cljc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
(string [this] (pr-str this)))
3333

3434
(defn sexpr-able?
35-
"Return true if [[sexpr]] is supported for `node`."
35+
"Return true if [[sexpr]] is supported for `node`'s element type.
36+
37+
See [related docs in user guide](/doc/01-user-guide.adoc#not-all-clojure-is-sexpr-able)"
3638
[node]
3739
(not (printable-only? node)))
3840

src/rewrite_clj/node/seq.cljc

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
(defn list-node
4343
"Create a node representing a list with `children`.
44-
44+
4545
```Clojure
4646
(require '[rewrite-clj.node :as n])
4747
@@ -60,7 +60,7 @@
6060
"Create a node representing a vector with `children`.
6161
6262
```Clojure
63-
(require '[rewrite-clj.node :as n])
63+
(require '[rewrite-clj.node :as n])
6464
6565
(-> (n/vector-node [(n/token-node 1)
6666
(n/spaces 1)
@@ -75,25 +75,36 @@
7575

7676
(defn set-node
7777
"Create a node representing a set with `children`.
78-
78+
7979
```Clojure
80-
(require '[rewrite-clj.node :as n])
80+
(require '[rewrite-clj.node :as n])
8181
8282
(-> (n/set-node [(n/token-node 1)
8383
(n/spaces 1)
8484
(n/token-node 2)
8585
(n/spaces 1)
8686
(n/token-node 3)])
87-
n/string)
87+
n/string)
8888
;; => \"#{1 2 3}\"
89-
```"
89+
```
90+
91+
Note that rewrite-clj allows the, technically illegal, set with duplicate values:
92+
```Clojure
93+
(-> (n/set-node [(n/token-node 1)
94+
(n/spaces 1)
95+
(n/token-node 1)])
96+
(n/string))
97+
;; => \"#{1 1}\"
98+
```
99+
100+
See [docs on sets with duplicate values](/doc/01-user-guide.adoc#sets-with-duplicate-values)."
90101
[children]
91102
(->SeqNode :set "#{%s}" 3 set children))
92103

93104
(defn map-node
94105
"Create a node representing a map with `children`.
95106
```Clojure
96-
(require '[rewrite-clj.node :as n])
107+
(require '[rewrite-clj.node :as n])
97108
98109
(-> (n/map-node [(n/keyword-node :a)
99110
(n/spaces 1)
@@ -103,15 +114,29 @@
103114
(n/spaces 1)
104115
(n/token-node 2)])
105116
(n/string))
106-
;; => \"{:a 1 :b 2}\"
117+
;; => \"{:a 1 :b 2}\"
107118
```
108119
109-
Note that rewrite-clj allows unbalanced maps:
120+
Note that rewrite-clj allows the, technically illegal, unbalanced map:
110121
```Clojure
111122
(-> (n/map-node [(n/keyword-node :a)])
112123
(n/string))
113124
;; => \"{:a}\"
114125
```
115-
Note also that [[sexpr]] will fail on an unbalanced map."
126+
See [docs on unbalanced maps](/doc/01-user-guide.adoc#unbalanced-maps).
127+
128+
Rewrite-clj also allows the, also technically illegal, map with duplicate keys:
129+
```Clojure
130+
(-> (n/map-node [(n/keyword-node :a)
131+
(n/spaces 1)
132+
(n/token-node 1)
133+
(n/spaces 1)
134+
(n/keyword-node :a)
135+
(n/spaces 1)
136+
(n/token-node 2)])
137+
(n/string))
138+
;; => \"{:a 1 :a 2}\"
139+
```
140+
See [docs on maps with duplicate keys](/doc/01-user-guide.adoc#maps-with-duplicate-keys)."
116141
[children]
117142
(->SeqNode :map "{%s}" 2 #(apply hash-map %) children))

src/rewrite_clj/zip.cljc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,9 @@
238238

239239
;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.base
240240
(defn sexpr-able?
241-
"Return true if current node in `zloc` can be [[sexpr]]-ed."
241+
"Return true if current node's element type in `zloc` can be [[sexpr]]-ed.
242+
243+
See [related docs in user guide](/doc/01-user-guide.adoc#not-all-clojure-is-sexpr-able)"
242244
[zloc] (rewrite-clj.zip.base/sexpr-able? zloc))
243245

244246
;; DO NOT EDIT FILE, automatically imported from: rewrite-clj.zip.base

src/rewrite_clj/zip/base.cljc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@
5858
(some-> zloc zraw/node node/tag))
5959

6060
(defn sexpr-able?
61-
"Return true if current node in `zloc` can be [[sexpr]]-ed."
61+
"Return true if current node's element type in `zloc` can be [[sexpr]]-ed.
62+
63+
See [related docs in user guide](/doc/01-user-guide.adoc#not-all-clojure-is-sexpr-able)"
6264
[zloc]
6365
(some-> zloc zraw/node node/sexpr-able?))
6466

0 commit comments

Comments
 (0)