Skip to content

Commit 4d4d1c1

Browse files
committed
feat: add custom CowStr type
Related issue: #20
1 parent 16491a4 commit 4d4d1c1

3 files changed

Lines changed: 115 additions & 26 deletions

File tree

src/attr.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::CowStr;
22
use crate::DiscontinuousString;
33
use crate::Span;
4-
use std::borrow::Cow;
54
use std::fmt;
65

76
use State::*;
@@ -114,19 +113,13 @@ impl<'s> Attributes<'s> {
114113
}
115114

116115
pub(crate) fn parse<S: DiscontinuousString<'s>>(&mut self, input: S) -> bool {
117-
#[inline]
118-
fn borrow(cow: CowStr) -> &str {
119-
match cow {
120-
Cow::Owned(_) => panic!(),
121-
Cow::Borrowed(s) => s,
122-
}
123-
}
124-
125116
for elem in Parser::new(input.chars()) {
126117
match elem {
127118
Element::Class(c) => self.insert("class", input.src(c).into()),
128119
Element::Identifier(i) => self.insert("id", input.src(i).into()),
129-
Element::Attribute(a, v) => self.insert(borrow(input.src(a)), input.src(v).into()),
120+
Element::Attribute(a, v) => {
121+
self.insert(input.src(a).take_borrowed(), input.src(v).into())
122+
}
130123
Element::Invalid => return false,
131124
}
132125
}

src/lib.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,16 @@ mod block;
5858
mod inline;
5959
mod lex;
6060
mod span;
61+
mod string;
6162
mod tree;
6263

6364
use span::DiscontinuousString;
6465
use span::Span;
6566

6667
pub use attr::{AttributeValue, AttributeValueParts, Attributes};
68+
pub use string::CowStr;
6769

68-
type CowStr<'s> = std::borrow::Cow<'s, str>;
70+
// type CowStr<'s> = std::borrow::Cow<'s, str>;
6971

7072
pub trait Render {
7173
/// Push [`Event`]s to a unicode-accepting buffer or stream.
@@ -695,10 +697,7 @@ impl<'s> Parser<'s> {
695697
inline::Container::InlineMath => Container::Math { display: false },
696698
inline::Container::DisplayMath => Container::Math { display: true },
697699
inline::Container::RawFormat => Container::RawInline {
698-
format: match self.inlines.src(inline.span) {
699-
CowStr::Owned(_) => panic!(),
700-
CowStr::Borrowed(s) => s,
701-
},
700+
format: self.inlines.src(inline.span).take_borrowed(),
702701
},
703702
inline::Container::Subscript => Container::Subscript,
704703
inline::Container::Superscript => Container::Superscript,
@@ -709,22 +708,22 @@ impl<'s> Parser<'s> {
709708
inline::Container::Mark => Container::Mark,
710709
inline::Container::InlineLink => Container::Link(
711710
match self.inlines.src(inline.span) {
712-
CowStr::Owned(s) => s.replace('\n', "").into(),
713711
s @ CowStr::Borrowed(_) => s,
712+
s => s.replace('\n', "").into(),
714713
},
715714
LinkType::Span(SpanLinkType::Inline),
716715
),
717716
inline::Container::InlineImage => Container::Image(
718717
match self.inlines.src(inline.span) {
719-
CowStr::Owned(s) => s.replace('\n', "").into(),
720718
s @ CowStr::Borrowed(_) => s,
719+
s => s.replace('\n', "").into(),
721720
},
722721
SpanLinkType::Inline,
723722
),
724723
inline::Container::ReferenceLink | inline::Container::ReferenceImage => {
725724
let tag = match self.inlines.src(inline.span) {
726-
CowStr::Owned(s) => s.replace('\n', " ").into(),
727725
s @ CowStr::Borrowed(_) => s,
726+
s => s.replace('\n', " ").into(),
728727
};
729728
let link_def =
730729
self.pre_pass.link_definitions.get(tag.as_ref()).cloned();
@@ -762,10 +761,7 @@ impl<'s> Parser<'s> {
762761
}
763762
inline::EventKind::Atom(a) => match a {
764763
inline::Atom::FootnoteReference => {
765-
let tag = match self.inlines.src(inline.span) {
766-
CowStr::Borrowed(s) => s,
767-
CowStr::Owned(..) => panic!(),
768-
};
764+
let tag = self.inlines.src(inline.span).take_borrowed();
769765
let number = self
770766
.footnote_references
771767
.iter()
@@ -778,10 +774,7 @@ impl<'s> Parser<'s> {
778774
|i| i + 1,
779775
);
780776
Event::FootnoteReference(
781-
match self.inlines.src(inline.span) {
782-
CowStr::Borrowed(s) => s,
783-
CowStr::Owned(..) => panic!(),
784-
},
777+
self.inlines.src(inline.span).take_borrowed(),
785778
number,
786779
)
787780
}

src/string.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use std::{borrow::Borrow, fmt::Display, ops::Deref, str::from_utf8};
2+
3+
// Largest CowStr variant is Owned(String). A String uses 3 words of memory, but a fourth word is
4+
// needed to hold the tag (the tag takes a byte, but a full word is used for alignment reasons.)
5+
// This means that the available space we have for an inline string is 4 words - 1 byte for the tag
6+
// and 1 word for encoding the length.
7+
const MAX_INLINE_STR_LEN: usize = 4 * std::mem::size_of::<usize>() - 2;
8+
9+
#[derive(Debug)]
10+
pub enum CowStr<'s> {
11+
Owned(String),
12+
Borrowed(&'s str),
13+
Inlined([u8; MAX_INLINE_STR_LEN], u8),
14+
}
15+
16+
impl<'s> CowStr<'s> {
17+
pub fn take_borrowed(self) -> &'s str {
18+
match self {
19+
CowStr::Borrowed(s) => s,
20+
CowStr::Owned(_) | CowStr::Inlined(..) => panic!(),
21+
}
22+
}
23+
}
24+
25+
impl<'s> Deref for CowStr<'s> {
26+
type Target = str;
27+
28+
fn deref(&self) -> &Self::Target {
29+
match *self {
30+
Self::Owned(ref s) => s.borrow(),
31+
Self::Borrowed(s) => s,
32+
// NOTE: Inlined strings can only be constructed from strings or chars, which means they
33+
// are guaranteed to be valid UTF-8. We could consider unchecked conversion as well, but
34+
// a benchmark should be done before introducing unsafes.
35+
Self::Inlined(ref inner, len) => from_utf8(&inner[..len as usize]).unwrap(),
36+
}
37+
}
38+
}
39+
40+
impl<'s> AsRef<str> for CowStr<'s> {
41+
fn as_ref(&self) -> &str {
42+
self.deref()
43+
}
44+
}
45+
46+
impl<'s> From<char> for CowStr<'s> {
47+
fn from(value: char) -> Self {
48+
let mut inner = [0u8; MAX_INLINE_STR_LEN];
49+
value.encode_utf8(&mut inner);
50+
CowStr::Inlined(inner, value.len_utf8() as u8)
51+
}
52+
}
53+
54+
impl<'s> From<&'s str> for CowStr<'s> {
55+
fn from(value: &'s str) -> Self {
56+
CowStr::Borrowed(value)
57+
}
58+
}
59+
60+
impl<'s> From<String> for CowStr<'s> {
61+
fn from(value: String) -> Self {
62+
CowStr::Owned(value)
63+
}
64+
}
65+
66+
impl<'s> Clone for CowStr<'s> {
67+
fn clone(&self) -> Self {
68+
match self {
69+
CowStr::Owned(s) => {
70+
let len = s.len();
71+
if len > MAX_INLINE_STR_LEN {
72+
CowStr::Owned(s.clone())
73+
} else {
74+
let mut inner = [0u8; MAX_INLINE_STR_LEN];
75+
inner[..len].copy_from_slice(s.as_bytes());
76+
CowStr::Inlined(inner, len as u8)
77+
}
78+
}
79+
CowStr::Borrowed(s) => CowStr::Borrowed(s),
80+
CowStr::Inlined(inner, len) => CowStr::Inlined(*inner, *len),
81+
}
82+
}
83+
}
84+
85+
impl<'s> PartialEq for CowStr<'s> {
86+
fn eq(&self, other: &Self) -> bool {
87+
self.deref() == other.deref()
88+
}
89+
}
90+
91+
impl<'s> Eq for CowStr<'s> {}
92+
93+
impl<'s> Display for CowStr<'s> {
94+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95+
f.write_str(self.deref())
96+
}
97+
}
98+
99+
impl<'s, 'a> FromIterator<&'a str> for CowStr<'s> {
100+
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
101+
CowStr::Owned(FromIterator::from_iter(iter))
102+
}
103+
}

0 commit comments

Comments
 (0)