edn/symbols.rs
1// Copyright 2018 Mozilla
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4// this file except in compliance with the License. You may obtain a copy of the
5// License at http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software distributed
7// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9// specific language governing permissions and limitations under the License.
10
11use std::fmt::{Display, Formatter, Write};
12
13use crate::namespaceable_name::NamespaceableName;
14
15#[macro_export]
16macro_rules! ns_keyword {
17 ($ns: expr, $name: expr) => {{
18 $crate::Keyword::namespaced($ns, $name)
19 }};
20}
21
22/// A simplification of Clojure's Symbol.
23#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
24pub struct PlainSymbol(pub String);
25
26#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
27pub struct NamespacedSymbol(NamespaceableName);
28
29/// A keyword is a symbol, optionally with a namespace, that prints with a leading colon.
30/// This concept is imported from Clojure, as it features in EDN and the query
31/// syntax that we use.
32///
33/// Clojure's constraints are looser than ours, allowing empty namespaces or
34/// names:
35///
36/// ```clojure
37/// user=> (keyword "" "")
38/// :/
39/// user=> (keyword "foo" "")
40/// :foo/
41/// user=> (keyword "" "bar")
42/// :/bar
43/// ```
44///
45/// We think that's nonsense, so we only allow keywords like `:bar` and `:foo/bar`,
46/// with both namespace and main parts containing no whitespace and no colon or slash:
47///
48/// ```rust
49/// # use edn::symbols::Keyword;
50/// let bar = Keyword::plain("bar"); // :bar
51/// let foo_bar = Keyword::namespaced("foo", "bar"); // :foo/bar
52/// assert_eq!("bar", bar.name());
53/// assert_eq!(None, bar.namespace());
54/// assert_eq!("bar", foo_bar.name());
55/// assert_eq!(Some("foo"), foo_bar.namespace());
56/// ```
57///
58/// If you're not sure whether your input is well-formed, you should use a
59/// parser or a reader function first to validate. TODO: implement `read`.
60///
61/// Callers are expected to follow these rules:
62/// http://www.clojure.org/reference/reader#_symbols
63///
64/// Future: fast equality (interning?) for keywords.
65///
66#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
67#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
68pub struct Keyword(NamespaceableName);
69
70impl PlainSymbol {
71 pub fn plain<T>(name: T) -> Self
72 where
73 T: Into<String>,
74 {
75 let n = name.into();
76 assert!(!n.is_empty(), "Symbols cannot be unnamed.");
77
78 PlainSymbol(n)
79 }
80
81 /// Return the name of the symbol without any leading '?' or '$'.
82 ///
83 /// ```rust
84 /// # use edn::symbols::PlainSymbol;
85 /// assert_eq!("foo", PlainSymbol::plain("?foo").name());
86 /// assert_eq!("foo", PlainSymbol::plain("$foo").name());
87 /// assert_eq!("!foo", PlainSymbol::plain("!foo").name());
88 /// ```
89 pub fn name(&self) -> &str {
90 if self.is_src_symbol() || self.is_var_symbol() {
91 &self.0[1..]
92 } else {
93 &self.0
94 }
95 }
96
97 #[inline]
98 pub fn is_var_symbol(&self) -> bool {
99 self.0.starts_with('?')
100 }
101
102 #[inline]
103 pub fn is_src_symbol(&self) -> bool {
104 self.0.starts_with('$')
105 }
106}
107
108impl NamespacedSymbol {
109 pub fn namespaced<N, T>(namespace: N, name: T) -> Self
110 where
111 N: AsRef<str>,
112 T: AsRef<str>,
113 {
114 let r = namespace.as_ref();
115 assert!(
116 !r.is_empty(),
117 "Namespaced symbols cannot have an empty non-null namespace."
118 );
119 NamespacedSymbol(NamespaceableName::namespaced(r, name))
120 }
121
122 #[inline]
123 pub fn name(&self) -> &str {
124 self.0.name()
125 }
126
127 #[inline]
128 pub fn namespace(&self) -> &str {
129 self.0.namespace().unwrap()
130 }
131
132 #[inline]
133 pub fn components(&self) -> (&str, &str) {
134 self.0.components()
135 }
136}
137
138impl Keyword {
139 pub fn plain<T>(name: T) -> Self
140 where
141 T: Into<String>,
142 {
143 Keyword(NamespaceableName::plain(name))
144 }
145}
146
147impl Keyword {
148 /// Creates a new `Keyword`.
149 ///
150 /// # Examples
151 ///
152 /// ```rust
153 /// # use edn::symbols::Keyword;
154 /// let keyword = Keyword::namespaced("foo", "bar");
155 /// assert_eq!(keyword.to_string(), ":foo/bar");
156 /// ```
157 ///
158 /// See also the `kw!` macro in the main `mentat` crate.
159 pub fn namespaced<N, T>(namespace: N, name: T) -> Self
160 where
161 N: AsRef<str>,
162 T: AsRef<str>,
163 {
164 let r = namespace.as_ref();
165 assert!(
166 !r.is_empty(),
167 "Namespaced keywords cannot have an empty non-null namespace."
168 );
169 Keyword(NamespaceableName::namespaced(r, name))
170 }
171
172 #[inline]
173 pub fn name(&self) -> &str {
174 self.0.name()
175 }
176
177 #[inline]
178 pub fn namespace(&self) -> Option<&str> {
179 self.0.namespace()
180 }
181
182 #[inline]
183 pub fn components(&self) -> (&str, &str) {
184 self.0.components()
185 }
186
187 /// Whether this `Keyword` should be interpreted in reverse order. For example,
188 /// the two following snippets are identical:
189 ///
190 /// ```edn
191 /// [?y :person/friend ?x]
192 /// [?x :person/hired ?y]
193 ///
194 /// [?y :person/friend ?x]
195 /// [?y :person/_hired ?x]
196 /// ```
197 ///
198 /// # Examples
199 ///
200 /// ```rust
201 /// # use edn::symbols::Keyword;
202 /// assert!(!Keyword::namespaced("foo", "bar").is_backward());
203 /// assert!(Keyword::namespaced("foo", "_bar").is_backward());
204 /// ```
205 #[inline]
206 pub fn is_backward(&self) -> bool {
207 self.0.is_backward()
208 }
209
210 /// Whether this `Keyword` should be interpreted in forward order.
211 /// See `symbols::Keyword::is_backward`.
212 ///
213 /// # Examples
214 ///
215 /// ```rust
216 /// # use edn::symbols::Keyword;
217 /// assert!(Keyword::namespaced("foo", "bar").is_forward());
218 /// assert!(!Keyword::namespaced("foo", "_bar").is_forward());
219 /// ```
220 #[inline]
221 pub fn is_forward(&self) -> bool {
222 self.0.is_forward()
223 }
224
225 #[inline]
226 pub fn is_namespaced(&self) -> bool {
227 self.0.is_namespaced()
228 }
229
230 /// Returns a `Keyword` with the same namespace and a
231 /// 'backward' name. See `symbols::Keyword::is_backward`.
232 ///
233 /// Returns a forward name if passed a reversed keyword; i.e., this
234 /// function is its own inverse.
235 ///
236 /// # Examples
237 ///
238 /// ```rust
239 /// # use edn::symbols::Keyword;
240 /// let nsk = Keyword::namespaced("foo", "bar");
241 /// assert!(!nsk.is_backward());
242 /// assert_eq!(":foo/bar", nsk.to_string());
243 ///
244 /// let reversed = nsk.to_reversed();
245 /// assert!(reversed.is_backward());
246 /// assert_eq!(":foo/_bar", reversed.to_string());
247 /// ```
248 pub fn to_reversed(&self) -> Keyword {
249 Keyword(self.0.to_reversed())
250 }
251
252 /// If this `Keyword` is 'backward' (see `symbols::Keyword::is_backward`),
253 /// return `Some('forward name')`; otherwise, return `None`.
254 ///
255 /// # Examples
256 ///
257 /// ```rust
258 /// # use edn::symbols::Keyword;
259 /// let nsk = Keyword::namespaced("foo", "bar");
260 /// assert_eq!(None, nsk.unreversed());
261 ///
262 /// let reversed = nsk.to_reversed();
263 /// assert_eq!(Some(nsk), reversed.unreversed());
264 /// ```
265 pub fn unreversed(&self) -> Option<Keyword> {
266 if self.is_backward() {
267 Some(self.to_reversed())
268 } else {
269 None
270 }
271 }
272}
273
274//
275// Note that we don't currently do any escaping.
276//
277
278impl Display for PlainSymbol {
279 /// Print the symbol in EDN format.
280 ///
281 /// # Examples
282 ///
283 /// ```rust
284 /// # use edn::symbols::PlainSymbol;
285 /// assert_eq!("baz", PlainSymbol::plain("baz").to_string());
286 /// ```
287 fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
288 self.0.fmt(f)
289 }
290}
291
292impl Display for NamespacedSymbol {
293 /// Print the symbol in EDN format.
294 ///
295 /// # Examples
296 ///
297 /// ```rust
298 /// # use edn::symbols::NamespacedSymbol;
299 /// assert_eq!("bar/baz", NamespacedSymbol::namespaced("bar", "baz").to_string());
300 /// ```
301 fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
302 self.0.fmt(f)
303 }
304}
305
306impl Display for Keyword {
307 /// Print the keyword in EDN format.
308 ///
309 /// # Examples
310 ///
311 /// ```rust
312 /// # use edn::symbols::Keyword;
313 /// assert_eq!(":baz", Keyword::plain("baz").to_string());
314 /// assert_eq!(":bar/baz", Keyword::namespaced("bar", "baz").to_string());
315 /// assert_eq!(":bar/_baz", Keyword::namespaced("bar", "baz").to_reversed().to_string());
316 /// assert_eq!(":bar/baz", Keyword::namespaced("bar", "baz").to_reversed().to_reversed().to_string());
317 /// ```
318 fn fmt(&self, f: &mut Formatter) -> ::std::fmt::Result {
319 f.write_char(':')?;
320 self.0.fmt(f)
321 }
322}
323
324#[test]
325fn test_ns_keyword_macro() {
326 assert_eq!(
327 ns_keyword!("test", "name").to_string(),
328 Keyword::namespaced("test", "name").to_string()
329 );
330 assert_eq!(
331 ns_keyword!("ns", "_name").to_string(),
332 Keyword::namespaced("ns", "_name").to_string()
333 );
334}