/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 | | } |