Skip to main content

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}