diff --git a/src/lib.rs b/src/lib.rs index 100a9c19..94a043b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,12 +58,14 @@ mod block; mod inline; mod lex; mod span; +mod string; mod tree; use span::DiscontinuousString; use span::Span; pub use attr::{AttributeValue, AttributeValueParts, Attributes}; +pub use string::{CowStr as JotdownCowStr, InlineStr}; type CowStr<'s> = std::borrow::Cow<'s, str>; diff --git a/src/string.rs b/src/string.rs new file mode 100644 index 00000000..cc91b002 --- /dev/null +++ b/src/string.rs @@ -0,0 +1,129 @@ +use std::{ + fmt::Display, + ops::{Deref, DerefMut}, + str::{from_utf8_unchecked, from_utf8_unchecked_mut}, +}; + +#[derive(Debug)] +pub enum CowStr<'s> { + Owned(String), + Borrowed(&'s str), + Inlined(InlineStr), +} + +impl<'s> Deref for CowStr<'s> { + type Target = str; + + fn deref(&self) -> &Self::Target { + match self { + Self::Owned(ref s) => s, + Self::Borrowed(s) => s, + Self::Inlined(s) => s.deref(), + } + } +} + +impl<'s> AsRef for CowStr<'s> { + fn as_ref(&self) -> &str { + self.deref() + } +} + +impl<'s> From<&'s str> for CowStr<'s> { + fn from(value: &'s str) -> Self { + CowStr::Borrowed(value) + } +} + +impl<'s> From for CowStr<'s> { + fn from(value: String) -> Self { + CowStr::Owned(value) + } +} + +impl<'s> Clone for CowStr<'s> { + fn clone(&self) -> Self { + match self { + CowStr::Owned(s) => match InlineStr::try_from(&**s) { + Ok(inline) => CowStr::Inlined(inline), + Err(_) => CowStr::Owned(s.clone()), + }, + CowStr::Borrowed(s) => CowStr::Borrowed(s), + CowStr::Inlined(s) => CowStr::Inlined(*s), + } + } +} + +impl<'s> PartialEq for CowStr<'s> { + fn eq(&self, other: &Self) -> bool { + self.deref() == other.deref() + } +} + +impl<'s> Eq for CowStr<'s> {} + +impl<'s> Display for CowStr<'s> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.deref()) + } +} + +impl<'s, 'a> FromIterator<&'a str> for CowStr<'s> { + fn from_iter>(iter: T) -> Self { + CowStr::Owned(FromIterator::from_iter(iter)) + } +} + +const MAX_INLINE_STR_LEN: usize = 3 * std::mem::size_of::() - 2; + +pub struct Error; + +#[derive(Clone, Copy, Debug)] +pub struct InlineStr { + inner: [u8; MAX_INLINE_STR_LEN], + len: usize, +} + +impl Deref for InlineStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + // SAFETY: `InlineStr` can only be constructed from strings or chars, which means they are + // guaranteed to be valid UTF-8. + unsafe { from_utf8_unchecked(&self.inner[..self.len]) } + } +} + +impl DerefMut for InlineStr { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: `InlineStr` can only be constructed from strings or chars, which means they are + // guaranteed to be valid UTF-8. + unsafe { from_utf8_unchecked_mut(&mut self.inner[..self.len]) } + } +} + +impl From for InlineStr { + fn from(value: char) -> Self { + let mut inner = [0u8; MAX_INLINE_STR_LEN]; + value.encode_utf8(&mut inner); + Self { + inner, + len: value.len_utf8(), + } + } +} + +impl TryFrom<&str> for InlineStr { + type Error = Error; + + fn try_from(value: &str) -> Result { + let len = value.len(); + if len > MAX_INLINE_STR_LEN { + Err(Error) + } else { + let mut inner = [0u8; MAX_INLINE_STR_LEN]; + inner.copy_from_slice(value.as_bytes()); + Ok(Self { inner, len }) + } + } +}