pulldown_cmark_fork/
strings.rs

1use std::ops::Deref;
2use std::borrow::{ToOwned, Borrow};
3use std::str::from_utf8;
4use std::hash::{Hash, Hasher};
5use std::convert::AsRef;
6use std::fmt;
7
8const MAX_INLINE_STR_LEN: usize = 3 * std::mem::size_of::<isize>() - 1;
9
10/// Returned when trying to convert a &str into a InlineStr
11/// but it fails because it doesn't fit.
12#[derive(Debug)]
13pub struct StringTooLongError;
14
15#[derive(Debug, Clone, Copy, Eq)]
16pub struct InlineStr {
17    inner: [u8; MAX_INLINE_STR_LEN],
18}
19
20impl<'a> AsRef<str> for InlineStr {
21    fn as_ref(&self) -> &str {
22        self.deref()
23    }
24}
25
26impl Hash for InlineStr {
27    fn hash<H: Hasher>(&self, state: &mut H) {
28        self.deref().hash(state);
29    }
30}
31
32impl From<char> for InlineStr {
33    fn from(c: char) -> Self {
34        let mut inner = [0u8; MAX_INLINE_STR_LEN];
35        c.encode_utf8(&mut inner);
36        inner[MAX_INLINE_STR_LEN - 1] = c.len_utf8() as u8;
37        Self { inner }
38    }
39}
40
41impl<'a> std::cmp::PartialEq<InlineStr> for InlineStr {
42    fn eq(&self, other: &InlineStr) -> bool {
43        self.deref() == other.deref()
44    }
45}
46
47// This could be an implementation of TryFrom<&str>
48// when that trait is stabilized.
49impl InlineStr {
50    pub fn try_from_str(s: &str) -> Result<InlineStr, StringTooLongError> {
51        let len = s.len();
52        if len < MAX_INLINE_STR_LEN {
53            let mut inner = [0u8; MAX_INLINE_STR_LEN];
54            inner[..len].copy_from_slice(s.as_bytes());
55            inner[MAX_INLINE_STR_LEN - 1] = len as u8;
56            Ok(Self { inner })
57        } else {
58            Err(StringTooLongError)
59        }
60    }
61}
62
63impl Deref for InlineStr {
64    type Target = str;
65
66    fn deref(&self) -> &str {
67        let len = self.inner[MAX_INLINE_STR_LEN - 1] as usize;
68        from_utf8(&self.inner[..len]).unwrap()
69    }
70}
71
72impl fmt::Display for InlineStr {
73    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74        write!(f, "{}", self.as_ref())
75    }
76}
77
78#[derive(Debug, Eq)]
79pub enum CowStr<'a> {
80    Boxed(Box<str>),
81    Borrowed(&'a str),
82    Inlined(InlineStr),
83}
84
85impl<'a> AsRef<str> for CowStr<'a> {
86    fn as_ref(&self) -> &str {
87        self.deref()
88    }
89}
90
91impl<'a> Hash for CowStr<'a> {
92    fn hash<H: Hasher>(&self, state: &mut H) {
93        self.deref().hash(state);
94    }
95}
96
97impl<'a> std::clone::Clone for CowStr<'a> {
98    fn clone(&self) -> Self {
99        match self {
100            CowStr::Boxed(s) if s.len() < MAX_INLINE_STR_LEN
101                => CowStr::Inlined(InlineStr::try_from_str(&**s).unwrap()),
102            CowStr::Boxed(s) => CowStr::Boxed(s.clone()),
103            CowStr::Borrowed(s) => CowStr::Borrowed(s),
104            CowStr::Inlined(s) => CowStr::Inlined(*s),
105        }
106    }
107}
108
109impl<'a> std::cmp::PartialEq<CowStr<'a>> for CowStr<'a> {
110    fn eq(&self, other: &CowStr) -> bool {
111        self.deref() == other.deref()
112    }
113}
114
115impl<'a> From<&'a str> for CowStr<'a> {
116    fn from(s: &'a str) -> Self {
117        CowStr::Borrowed(s)
118    }
119}
120
121impl<'a> From<String> for CowStr<'a> {
122    fn from(s: String) -> Self {
123        CowStr::Boxed(s.into_boxed_str())
124    }
125}
126
127impl<'a> From<char> for CowStr<'a> {
128    fn from(c: char) -> Self {
129        CowStr::Inlined(c.into())
130    }
131}
132
133impl<'a> Deref for CowStr<'a> {
134    type Target = str;
135
136    fn deref(&self) -> &str {
137        match self {
138            CowStr::Boxed(ref b) => &*b,
139            CowStr::Borrowed(b) => b,
140            CowStr::Inlined(ref s) => s.deref(),
141        }
142    }
143}
144
145impl<'a> Borrow<str> for CowStr<'a> {
146    fn borrow(&self) -> &str {
147        self.deref()
148    }
149}
150
151impl<'a> CowStr<'a> {
152    pub fn into_string(self) -> String {
153        match self {
154            CowStr::Boxed(b) => b.into(),
155            CowStr::Borrowed(b) => b.to_owned(),
156            CowStr::Inlined(s) => s.deref().to_owned(),
157        }        
158    }
159}
160
161impl<'a> fmt::Display for CowStr<'a> {
162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163        write!(f, "{}", self.as_ref())
164    }
165}
166
167#[cfg(test)]
168mod test_special_string {
169    use super::*;
170
171    #[test]
172    fn inlinestr_ascii() {
173        let s: InlineStr = 'a'.into();
174        assert_eq!("a", s.deref());
175    }
176
177    #[test]
178    fn inlinestr_unicode() {
179        let s: InlineStr = '🍔'.into();
180        assert_eq!("🍔", s.deref());
181    }
182
183    #[test]
184    fn cowstr_size() {
185        let size = std::mem::size_of::<CowStr>();
186        let word_size = std::mem::size_of::<isize>();
187        assert_eq!(3 * word_size, size);
188    }
189
190    #[test]
191    fn cowstr_char_to_string() {
192        let c = '藏';
193        let smort: CowStr = c.into();
194        let owned: String = smort.to_string();
195        let expected = "藏".to_owned();
196        assert_eq!(expected, owned);
197    }
198
199    #[test]
200    fn max_inline_str_len_atleast_five() {
201        // we need 4 bytes to store a char and then one more to store
202        // its length
203        assert!(MAX_INLINE_STR_LEN >= 5);
204    }
205
206    #[test]
207    #[cfg(target_pointer_width = "64")]
208    fn inlinestr_fits_twentytwo() {
209        let s = "0123456789abcdefghijkl";
210        let stack_str = InlineStr::try_from_str(s).unwrap();
211        assert_eq!(stack_str.deref(), s);
212    }
213
214    #[test]
215    #[cfg(target_pointer_width = "64")]
216    fn inlinestr_not_fits_twentythree() {
217        let s = "0123456789abcdefghijklm";
218        let _stack_str = InlineStr::try_from_str(s).unwrap_err();
219    }
220
221    #[test]
222    #[cfg(target_pointer_width = "64")]
223    fn small_boxed_str_clones_to_stack() {
224        let s = "0123456789abcde".to_owned();
225        let smort: CowStr = s.into();
226        let smort_clone = smort.clone();
227
228        if let CowStr::Inlined(..) = smort_clone {} else {
229            panic!("Expected a Inlined variant!");
230        }
231    }
232}
233