ion_rs/types/
symbol.rs

1use crate::ion_data::{IonEq, IonOrd};
2use crate::result::decoding_error;
3use crate::{IonResult, SymbolRef};
4use std::borrow::Borrow;
5use std::cmp::Ordering;
6use std::fmt::{Display, Formatter};
7use std::hash::{Hash, Hasher};
8use std::sync::Arc;
9
10/// Stores or points to the text of a given [Symbol].
11#[derive(Debug, Eq)]
12enum SymbolText {
13    // This Symbol refers to a string in the symbol table
14    Shared(Arc<str>),
15    // This Symbol owns its own text
16    Owned(String),
17    // This Symbol is equivalent to SID zero (`$0`)
18    Unknown,
19}
20
21impl SymbolText {
22    fn text(&self) -> Option<&str> {
23        let text = match self {
24            SymbolText::Shared(s) => s.as_ref(),
25            SymbolText::Owned(s) => s.as_str(),
26            SymbolText::Unknown => return None,
27        };
28        Some(text)
29    }
30}
31
32impl Hash for SymbolText {
33    fn hash<H: Hasher>(&self, state: &mut H) {
34        match self {
35            SymbolText::Shared(text) => text.hash(state),
36            SymbolText::Owned(text) => text.hash(state),
37            SymbolText::Unknown => 0.hash(state),
38        }
39    }
40}
41
42impl Clone for SymbolText {
43    fn clone(&self) -> Self {
44        match self {
45            SymbolText::Owned(text) => SymbolText::Owned(text.to_owned()),
46            SymbolText::Shared(text) => SymbolText::Shared(Arc::clone(text)),
47            SymbolText::Unknown => SymbolText::Unknown,
48        }
49    }
50}
51
52impl PartialEq<Self> for SymbolText {
53    fn eq(&self, other: &Self) -> bool {
54        self.cmp(other) == Ordering::Equal
55    }
56}
57
58impl PartialOrd<Self> for SymbolText {
59    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
60        Some(self.cmp(other))
61    }
62}
63
64impl Ord for SymbolText {
65    fn cmp(&self, other: &Self) -> Ordering {
66        match (self.text(), other.text()) {
67            // If both Symbols have known text, delegate the comparison to their text.
68            (Some(s1), Some(s2)) => s1.cmp(s2),
69            // Otherwise, $0 (unknown text) is treated as 'less than' known text
70            (Some(_), None) => Ordering::Greater,
71            (None, Some(_)) => Ordering::Less,
72            (None, None) => Ordering::Equal,
73        }
74    }
75}
76
77/// The text of a fully resolved field name, annotation, or symbol value. If the symbol has known
78/// text (that is: the symbol is not `$0`), it will be stored as either a `String` or a shared
79/// reference to text in a symbol table.
80#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
81pub struct Symbol {
82    text: SymbolText,
83}
84
85impl Symbol {
86    pub fn owned<I: Into<String>>(text: I) -> Symbol {
87        Symbol {
88            text: SymbolText::Owned(text.into()),
89        }
90    }
91
92    pub fn shared(text: Arc<str>) -> Symbol {
93        Symbol {
94            text: SymbolText::Shared(text),
95        }
96    }
97
98    pub fn unknown_text() -> Symbol {
99        Symbol {
100            text: SymbolText::Unknown,
101        }
102    }
103
104    /// Converts this symbol into a shared one
105    pub(crate) fn into_shared(self) -> Symbol {
106        match self.text {
107            SymbolText::Shared(text) => Symbol::shared(text),
108            SymbolText::Owned(text) => Symbol::shared(text.into()),
109            SymbolText::Unknown => Symbol::unknown_text(),
110        }
111    }
112
113    pub fn text(&self) -> Option<&str> {
114        self.text.text()
115    }
116
117    pub fn text_or_error(&self) -> IonResult<&str> {
118        match self.text() {
119            Some(text) => Ok(text),
120            None => decoding_error("symbol has unknown text"),
121        }
122    }
123}
124
125impl IonEq for Symbol {
126    fn ion_eq(&self, other: &Self) -> bool {
127        self == other
128    }
129}
130
131impl IonOrd for Symbol {
132    fn ion_cmp(&self, other: &Self) -> Ordering {
133        self.cmp(other)
134    }
135}
136
137// We cannot use a blanket impl for AsRef<str> as that would prevent us from
138// optimizing the From<String> case, a conversion which can be performed
139// without cloning.
140
141impl From<&str> for Symbol {
142    fn from(text: &str) -> Self {
143        Symbol::owned(text)
144    }
145}
146
147impl From<String> for Symbol {
148    fn from(text: String) -> Self {
149        Symbol::owned(text)
150    }
151}
152
153impl From<&String> for Symbol {
154    fn from(text: &String) -> Self {
155        text.as_str().into()
156    }
157}
158
159impl<'a> From<&'a Symbol> for Symbol {
160    fn from(text: &'a Symbol) -> Self {
161        text.clone()
162    }
163}
164
165impl<'a> From<SymbolRef<'a>> for Symbol {
166    fn from(symbol_ref: SymbolRef<'a>) -> Self {
167        symbol_ref.to_owned()
168    }
169}
170
171impl Display for Symbol {
172    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
173        match self.text() {
174            None => write!(f, "$0"),
175            Some(text) => write!(f, "'{text}'"),
176        }
177    }
178}
179
180impl<A: AsRef<str>> PartialEq<A> for Symbol {
181    fn eq(&self, other: &A) -> bool {
182        self.text()
183            // If the symbol has known text, compare it to the provide text
184            .map(|t| t == other.as_ref())
185            // If there's no text, it's definitely not equivalent to the provided text
186            .unwrap_or(false)
187    }
188}
189
190impl PartialEq<Symbol> for String {
191    fn eq(&self, other: &Symbol) -> bool {
192        other.text().map(|t| t == self.as_str()).unwrap_or(false)
193    }
194}
195
196impl PartialEq<Symbol> for &str {
197    fn eq(&self, other: &Symbol) -> bool {
198        other.text().map(|t| &t == self).unwrap_or(false)
199    }
200}
201
202// Note that this method panics if the Symbol has unknown text! This is unfortunate but is required
203// in order to allow a HashMap<Symbol, _> to do lookups with a &str instead of a &Symbol
204impl Borrow<str> for Symbol {
205    fn borrow(&self) -> &str {
206        self.text()
207            .expect("cannot borrow a &str from a Symbol with unknown text")
208    }
209}
210
211#[cfg(test)]
212mod symbol_tests {
213    use super::*;
214
215    #[test]
216    fn ordering_and_eq() {
217        let mut symbols = vec![
218            Symbol::owned("foo"),
219            Symbol::shared(Arc::from("bar")),
220            Symbol::shared(Arc::from("baz")),
221            Symbol::owned("quux"),
222        ];
223        // Sort the list to demonstrate that our Ord implementation works.
224        symbols.as_mut_slice().sort();
225        // Equality testing doesn't depend on what kind of Symbol it is, just the text.
226        // We can compare the sorted version of the vec above to this one and it will
227        // be considered equal.
228        let expected = vec![
229            Symbol::owned("bar"),
230            Symbol::owned("baz"),
231            Symbol::owned("foo"),
232            Symbol::owned("quux"),
233        ];
234        assert_eq!(symbols, expected)
235    }
236}