Skip to main content

satteri_pulldown_cmark/
strings.rs

1use alloc::{
2    borrow::{Cow, ToOwned},
3    boxed::Box,
4    string::{String, ToString},
5};
6use core::{
7    borrow::Borrow,
8    fmt,
9    hash::{Hash, Hasher},
10    ops::Deref,
11    str::from_utf8,
12};
13
14const MAX_INLINE_STR_LEN: usize = 3 * core::mem::size_of::<isize>() - 2;
15
16/// Returned when trying to convert a `&str` into a `InlineStr`
17/// but it fails because it doesn't fit.
18#[derive(Debug)]
19pub struct StringTooLongError;
20
21/// An inline string that can contain almost three words
22/// of utf-8 text.
23#[derive(Debug, Clone, Copy, Eq)]
24pub struct InlineStr {
25    inner: [u8; MAX_INLINE_STR_LEN],
26    len: u8,
27}
28
29impl AsRef<str> for InlineStr {
30    fn as_ref(&self) -> &str {
31        self.deref()
32    }
33}
34
35impl Hash for InlineStr {
36    fn hash<H: Hasher>(&self, state: &mut H) {
37        self.deref().hash(state);
38    }
39}
40
41impl From<char> for InlineStr {
42    fn from(c: char) -> Self {
43        let mut inner = [0u8; MAX_INLINE_STR_LEN];
44        c.encode_utf8(&mut inner);
45        let len = c.len_utf8() as u8;
46        Self { inner, len }
47    }
48}
49
50impl core::cmp::PartialEq<InlineStr> for InlineStr {
51    fn eq(&self, other: &InlineStr) -> bool {
52        self.deref() == other.deref()
53    }
54}
55
56impl TryFrom<&str> for InlineStr {
57    type Error = StringTooLongError;
58
59    fn try_from(s: &str) -> Result<InlineStr, StringTooLongError> {
60        let len = s.len();
61        if len <= MAX_INLINE_STR_LEN {
62            let mut inner = [0u8; MAX_INLINE_STR_LEN];
63            inner[..len].copy_from_slice(s.as_bytes());
64            let len = len as u8;
65            Ok(Self { inner, len })
66        } else {
67            Err(StringTooLongError)
68        }
69    }
70}
71
72impl Deref for InlineStr {
73    type Target = str;
74
75    fn deref(&self) -> &str {
76        let len = self.len as usize;
77        from_utf8(&self.inner[..len]).unwrap()
78    }
79}
80
81impl fmt::Display for InlineStr {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "{}", self.as_ref())
84    }
85}
86
87/// A copy-on-write string that can be owned, borrowed
88/// or inlined.
89///
90/// It is three words long.
91#[derive(Debug, Eq)]
92pub enum CowStr<'a> {
93    /// An owned, immutable string.
94    Boxed(Box<str>),
95    /// A borrowed string.
96    Borrowed(&'a str),
97    /// A short inline string.
98    Inlined(InlineStr),
99}
100
101#[cfg(feature = "serde")]
102mod serde_impl {
103    use core::fmt;
104
105    use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
106
107    use super::CowStr;
108
109    impl<'a> Serialize for CowStr<'a> {
110        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111        where
112            S: Serializer,
113        {
114            serializer.serialize_str(self.as_ref())
115        }
116    }
117
118    struct CowStrVisitor;
119
120    impl<'de> de::Visitor<'de> for CowStrVisitor {
121        type Value = CowStr<'de>;
122
123        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
124            formatter.write_str("a string")
125        }
126
127        fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
128        where
129            E: de::Error,
130        {
131            Ok(CowStr::Borrowed(v))
132        }
133
134        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
135        where
136            E: de::Error,
137        {
138            match v.try_into() {
139                Ok(it) => Ok(CowStr::Inlined(it)),
140                Err(_) => Ok(CowStr::Boxed(String::from(v).into_boxed_str())),
141            }
142        }
143
144        fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
145        where
146            E: de::Error,
147        {
148            Ok(CowStr::Boxed(v.into_boxed_str()))
149        }
150    }
151
152    impl<'a, 'de: 'a> Deserialize<'de> for CowStr<'a> {
153        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154        where
155            D: Deserializer<'de>,
156        {
157            deserializer.deserialize_str(CowStrVisitor)
158        }
159    }
160}
161
162impl<'a> AsRef<str> for CowStr<'a> {
163    fn as_ref(&self) -> &str {
164        self.deref()
165    }
166}
167
168impl<'a> Hash for CowStr<'a> {
169    fn hash<H: Hasher>(&self, state: &mut H) {
170        self.deref().hash(state);
171    }
172}
173
174impl<'a> core::clone::Clone for CowStr<'a> {
175    fn clone(&self) -> Self {
176        match self {
177            CowStr::Boxed(s) => match InlineStr::try_from(&**s) {
178                Ok(inline) => CowStr::Inlined(inline),
179                Err(..) => CowStr::Boxed(s.clone()),
180            },
181            CowStr::Borrowed(s) => CowStr::Borrowed(s),
182            CowStr::Inlined(s) => CowStr::Inlined(*s),
183        }
184    }
185}
186
187impl<'a> core::cmp::PartialEq<CowStr<'a>> for CowStr<'a> {
188    fn eq(&self, other: &CowStr<'_>) -> bool {
189        self.deref() == other.deref()
190    }
191}
192
193impl<'a> From<&'a str> for CowStr<'a> {
194    fn from(s: &'a str) -> Self {
195        CowStr::Borrowed(s)
196    }
197}
198
199impl<'a> From<String> for CowStr<'a> {
200    fn from(s: String) -> Self {
201        CowStr::Boxed(s.into_boxed_str())
202    }
203}
204
205impl<'a> From<char> for CowStr<'a> {
206    fn from(c: char) -> Self {
207        CowStr::Inlined(c.into())
208    }
209}
210
211impl<'a> From<Cow<'a, str>> for CowStr<'a> {
212    fn from(s: Cow<'a, str>) -> Self {
213        match s {
214            Cow::Borrowed(s) => CowStr::Borrowed(s),
215            Cow::Owned(s) => CowStr::Boxed(s.into_boxed_str()),
216        }
217    }
218}
219
220impl<'a> From<CowStr<'a>> for Cow<'a, str> {
221    fn from(s: CowStr<'a>) -> Self {
222        match s {
223            CowStr::Boxed(s) => Cow::Owned(s.to_string()),
224            CowStr::Inlined(s) => Cow::Owned(s.to_string()),
225            CowStr::Borrowed(s) => Cow::Borrowed(s),
226        }
227    }
228}
229
230impl<'a> From<Cow<'a, char>> for CowStr<'a> {
231    fn from(s: Cow<'a, char>) -> Self {
232        CowStr::Inlined(InlineStr::from(*s))
233    }
234}
235
236impl<'a> From<CowStr<'a>> for String {
237    fn from(s: CowStr<'a>) -> Self {
238        match s {
239            CowStr::Boxed(s) => s.into(),
240            CowStr::Inlined(s) => s.as_ref().into(),
241            CowStr::Borrowed(s) => s.into(),
242        }
243    }
244}
245
246impl<'a> Deref for CowStr<'a> {
247    type Target = str;
248
249    fn deref(&self) -> &str {
250        match self {
251            CowStr::Boxed(ref b) => b,
252            CowStr::Borrowed(b) => b,
253            CowStr::Inlined(ref s) => s.deref(),
254        }
255    }
256}
257
258impl<'a> Borrow<str> for CowStr<'a> {
259    fn borrow(&self) -> &str {
260        self.deref()
261    }
262}
263
264impl<'a> CowStr<'a> {
265    pub fn into_string(self) -> String {
266        match self {
267            CowStr::Boxed(b) => b.into(),
268            CowStr::Borrowed(b) => b.to_owned(),
269            CowStr::Inlined(s) => s.deref().to_owned(),
270        }
271    }
272
273    pub fn into_static(self) -> CowStr<'static> {
274        match self {
275            CowStr::Boxed(b) => CowStr::Boxed(b),
276            CowStr::Borrowed(b) => match InlineStr::try_from(b) {
277                Ok(inline) => CowStr::Inlined(inline),
278                Err(_) => CowStr::Boxed(b.into()),
279            },
280            CowStr::Inlined(s) => CowStr::Inlined(s),
281        }
282    }
283}
284
285impl<'a> fmt::Display for CowStr<'a> {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        write!(f, "{}", self.as_ref())
288    }
289}
290
291#[cfg(test)]
292mod test_special_string {
293    use alloc::{
294        borrow::ToOwned,
295        string::{String, ToString},
296    };
297
298    use super::*;
299
300    #[test]
301    fn inlinestr_ascii() {
302        let s: InlineStr = 'a'.into();
303        assert_eq!("a", s.deref());
304    }
305
306    #[test]
307    fn inlinestr_unicode() {
308        let s: InlineStr = '🍔'.into();
309        assert_eq!("🍔", s.deref());
310    }
311
312    #[test]
313    fn cowstr_size() {
314        let size = core::mem::size_of::<CowStr>();
315        let word_size = core::mem::size_of::<isize>();
316        assert_eq!(3 * word_size, size);
317    }
318
319    #[test]
320    fn cowstr_char_to_string() {
321        let c = '藏';
322        let smort: CowStr = c.into();
323        let owned: String = smort.to_string();
324        let expected = "藏".to_owned();
325        assert_eq!(expected, owned);
326    }
327
328    #[test]
329    #[cfg(target_pointer_width = "64")]
330    fn inlinestr_fits_twentytwo() {
331        let s = "0123456789abcdefghijkl";
332        let stack_str = InlineStr::try_from(s).unwrap();
333        assert_eq!(stack_str.deref(), s);
334    }
335
336    #[test]
337    #[cfg(target_pointer_width = "64")]
338    fn inlinestr_not_fits_twentythree() {
339        let s = "0123456789abcdefghijklm";
340        let _stack_str = InlineStr::try_from(s).unwrap_err();
341    }
342
343    #[test]
344    #[cfg(target_pointer_width = "64")]
345    fn small_boxed_str_clones_to_stack() {
346        let s = "0123456789abcde".to_owned();
347        let smort: CowStr = s.into();
348        let smort_clone = smort.clone();
349
350        if let CowStr::Inlined(..) = smort_clone {
351        } else {
352            panic!("Expected a Inlined variant!");
353        }
354    }
355
356    #[test]
357    fn cow_to_cow_str() {
358        let s = "some text";
359        let cow = Cow::Borrowed(s);
360        let actual = CowStr::from(cow);
361        let expected = CowStr::Borrowed(s);
362        assert_eq!(actual, expected);
363        assert!(variant_eq(&actual, &expected));
364
365        let s = "some text".to_string();
366        let cow: Cow<str> = Cow::Owned(s.clone());
367        let actual = CowStr::from(cow);
368        let expected = CowStr::Boxed(s.into_boxed_str());
369        assert_eq!(actual, expected);
370        assert!(variant_eq(&actual, &expected));
371    }
372
373    #[test]
374    fn cow_str_to_cow() {
375        let s = "some text";
376        let cow_str = CowStr::Borrowed(s);
377        let actual = Cow::from(cow_str);
378        let expected = Cow::Borrowed(s);
379        assert_eq!(actual, expected);
380        assert!(variant_eq(&actual, &expected));
381
382        let s = "s";
383        let inline_str: InlineStr = InlineStr::try_from(s).unwrap();
384        let cow_str = CowStr::Inlined(inline_str);
385        let actual = Cow::from(cow_str);
386        let expected: Cow<str> = Cow::Owned(s.to_string());
387        assert_eq!(actual, expected);
388        assert!(variant_eq(&actual, &expected));
389
390        let s = "s";
391        let cow_str = CowStr::Boxed(s.to_string().into_boxed_str());
392        let actual = Cow::from(cow_str);
393        let expected: Cow<str> = Cow::Owned(s.to_string());
394        assert_eq!(actual, expected);
395        assert!(variant_eq(&actual, &expected));
396    }
397
398    #[test]
399    fn cow_str_to_string() {
400        let s = "some text";
401        let cow_str = CowStr::Borrowed(s);
402        let actual = String::from(cow_str);
403        let expected = String::from("some text");
404        assert_eq!(actual, expected);
405
406        let s = "s";
407        let inline_str: InlineStr = InlineStr::try_from(s).unwrap();
408        let cow_str = CowStr::Inlined(inline_str);
409        let actual = String::from(cow_str);
410        let expected = String::from("s");
411        assert_eq!(actual, expected);
412
413        let s = "s";
414        let cow_str = CowStr::Boxed(s.to_string().into_boxed_str());
415        let actual = String::from(cow_str);
416        let expected = String::from("s");
417        assert_eq!(actual, expected);
418    }
419
420    #[test]
421    fn cow_char_to_cow_str() {
422        let c = 'c';
423        let cow: Cow<char> = Cow::Owned(c);
424        let actual = CowStr::from(cow);
425        let expected = CowStr::Inlined(InlineStr::from(c));
426        assert_eq!(actual, expected);
427        assert!(variant_eq(&actual, &expected));
428
429        let c = 'c';
430        let cow: Cow<char> = Cow::Borrowed(&c);
431        let actual = CowStr::from(cow);
432        let expected = CowStr::Inlined(InlineStr::from(c));
433        assert_eq!(actual, expected);
434        assert!(variant_eq(&actual, &expected));
435    }
436
437    fn variant_eq<T>(a: &T, b: &T) -> bool {
438        core::mem::discriminant(a) == core::mem::discriminant(b)
439    }
440}