asciidoc_parser/
strings.rs

1// Adapted from pulldown-cmark, which comes with the following license:
2//
3// Copyright 2015 Google Inc. All rights reserved.
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21// THE SOFTWARE.
22
23//! String types that facilitate parsing.
24
25use std::{
26    borrow::{Borrow, Cow},
27    fmt,
28    hash::{Hash, Hasher},
29    ops::Deref,
30    str::from_utf8,
31};
32
33pub(crate) const MAX_INLINE_STR_LEN: usize = 3 * std::mem::size_of::<isize>() - 2;
34
35/// Returned when trying to convert a `&str` into an [`InlineStr`] but it fails
36/// because it doesn't fit.
37#[derive(Debug)]
38pub struct StringTooLongError;
39
40/// An inline string that can contain almost three words
41/// of UTF-8 text.
42#[derive(Debug, Clone, Copy, Eq)]
43pub struct InlineStr {
44    inner: [u8; MAX_INLINE_STR_LEN],
45    len: u8,
46}
47
48impl AsRef<str> for InlineStr {
49    fn as_ref(&self) -> &str {
50        self.deref()
51    }
52}
53
54impl Hash for InlineStr {
55    fn hash<H: Hasher>(&self, state: &mut H) {
56        self.deref().hash(state);
57    }
58}
59
60impl From<char> for InlineStr {
61    fn from(c: char) -> Self {
62        let mut inner = [0u8; MAX_INLINE_STR_LEN];
63        c.encode_utf8(&mut inner);
64        let len = c.len_utf8() as u8;
65        Self { inner, len }
66    }
67}
68
69impl std::cmp::PartialEq<InlineStr> for InlineStr {
70    fn eq(&self, other: &InlineStr) -> bool {
71        self.deref() == other.deref()
72    }
73}
74
75impl TryFrom<&str> for InlineStr {
76    type Error = StringTooLongError;
77
78    fn try_from(s: &str) -> Result<InlineStr, StringTooLongError> {
79        let len = s.len();
80        if len <= MAX_INLINE_STR_LEN {
81            let mut inner = [0u8; MAX_INLINE_STR_LEN];
82            inner[..len].copy_from_slice(s.as_bytes());
83            let len = len as u8;
84            Ok(Self { inner, len })
85        } else {
86            Err(StringTooLongError)
87        }
88    }
89}
90
91impl Deref for InlineStr {
92    type Target = str;
93
94    fn deref(&self) -> &str {
95        let len = self.len as usize;
96        from_utf8(&self.inner[..len]).unwrap_or_default()
97    }
98}
99
100impl fmt::Display for InlineStr {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        write!(f, "{}", self.as_ref())
103    }
104}
105
106/// A copy-on-write string that can be owned, borrowed
107/// or inlined.
108///
109/// It is three words long.
110#[derive(Debug, Eq)]
111pub enum CowStr<'a> {
112    /// An owned, immutable string.
113    Boxed(Box<str>),
114    /// A borrowed string.
115    Borrowed(&'a str),
116    /// A short inline string.
117    Inlined(InlineStr),
118}
119
120impl AsRef<str> for CowStr<'_> {
121    fn as_ref(&self) -> &str {
122        self.deref()
123    }
124}
125
126impl Hash for CowStr<'_> {
127    fn hash<H: Hasher>(&self, state: &mut H) {
128        self.deref().hash(state);
129    }
130}
131
132impl std::clone::Clone for CowStr<'_> {
133    fn clone(&self) -> Self {
134        match self {
135            CowStr::Boxed(s) => match InlineStr::try_from(&**s) {
136                Ok(inline) => CowStr::Inlined(inline),
137                Err(..) => CowStr::Boxed(s.clone()),
138            },
139            CowStr::Borrowed(s) => CowStr::Borrowed(s),
140            CowStr::Inlined(s) => CowStr::Inlined(*s),
141        }
142    }
143}
144
145impl<'a> std::cmp::PartialEq<CowStr<'a>> for CowStr<'a> {
146    fn eq(&self, other: &CowStr<'_>) -> bool {
147        self.deref() == other.deref()
148    }
149}
150
151impl<'a> From<&'a str> for CowStr<'a> {
152    fn from(s: &'a str) -> Self {
153        CowStr::Borrowed(s)
154    }
155}
156
157impl From<String> for CowStr<'_> {
158    fn from(s: String) -> Self {
159        CowStr::Boxed(s.into_boxed_str())
160    }
161}
162
163impl From<char> for CowStr<'_> {
164    fn from(c: char) -> Self {
165        CowStr::Inlined(c.into())
166    }
167}
168
169impl<'a> From<Cow<'a, str>> for CowStr<'a> {
170    fn from(s: Cow<'a, str>) -> Self {
171        match s {
172            Cow::Borrowed(s) => CowStr::Borrowed(s),
173            Cow::Owned(s) => CowStr::Boxed(s.into_boxed_str()),
174        }
175    }
176}
177
178impl<'a> From<CowStr<'a>> for Cow<'a, str> {
179    fn from(s: CowStr<'a>) -> Self {
180        match s {
181            CowStr::Boxed(s) => Cow::Owned(s.to_string()),
182            CowStr::Inlined(s) => Cow::Owned(s.to_string()),
183            CowStr::Borrowed(s) => Cow::Borrowed(s),
184        }
185    }
186}
187
188impl<'a> From<Cow<'a, char>> for CowStr<'a> {
189    fn from(s: Cow<'a, char>) -> Self {
190        CowStr::Inlined(InlineStr::from(*s))
191    }
192}
193
194impl Deref for CowStr<'_> {
195    type Target = str;
196
197    fn deref(&self) -> &str {
198        match self {
199            CowStr::Boxed(ref b) => b,
200            CowStr::Borrowed(b) => b,
201            CowStr::Inlined(ref s) => s.deref(),
202        }
203    }
204}
205
206impl Borrow<str> for CowStr<'_> {
207    fn borrow(&self) -> &str {
208        self.deref()
209    }
210}
211
212impl CowStr<'_> {
213    /// Convert the `CowStr` into an owned `String`.
214    pub fn into_string(self) -> String {
215        match self {
216            CowStr::Boxed(b) => b.into(),
217            CowStr::Borrowed(b) => b.to_owned(),
218            CowStr::Inlined(s) => s.deref().to_owned(),
219        }
220    }
221}
222
223impl fmt::Display for CowStr<'_> {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        write!(f, "{}", self.as_ref())
226    }
227}