Coverage Report

Created: 2023-04-25 07:07

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