Skip to main content

german_str_borrow/
lib.rs

1extern crate core;
2
3use crate::long_str::{Class, LongBStr};
4use crate::short_str::ShortBStr;
5use bstr::BStr;
6use std::cmp::Ordering;
7use std::fmt::{Debug, Display, Formatter};
8use std::hash::{Hash, Hasher};
9use std::ops::Deref;
10use std::slice::SliceIndex;
11
12#[derive(Clone, Copy)]
13#[repr(transparent)]
14pub struct GermanBStr<'a>(BStrInner<'a>);
15pub use long_str::StrAllocator;
16
17impl Default for GermanBStr<'_> {
18    fn default() -> Self {
19        Self::new_static(&[])
20    }
21}
22
23#[cfg(feature = "bumpalo")]
24mod bumpalo;
25mod long_str;
26mod short_str;
27
28#[derive(Clone, Copy)]
29#[repr(C)]
30union BStrInner<'a> {
31    head: BStrHead,
32    short: ShortBStr,
33    long: LongBStr<'a>,
34}
35
36#[derive(Clone, Copy, Eq, PartialEq)]
37#[repr(C)]
38struct BStrHead {
39    len: u32,
40    head: [u8; 4],
41}
42
43impl<'a> GermanBStr<'a> {
44    pub fn is_empty(self) -> bool {
45        self.len() == 0
46    }
47
48    pub fn len(self) -> usize {
49        self.head().len as usize
50    }
51
52    fn head_bytes(&self) -> &[u8; 4] {
53        &self.head().head
54    }
55
56    fn is_short(self) -> bool {
57        self.len() <= 12
58    }
59
60    fn head(&self) -> &BStrHead {
61        unsafe { &self.0.head }
62    }
63
64    fn as_long(&self) -> Option<&LongBStr<'a>> {
65        if self.is_short() {
66            return None;
67        };
68        unsafe { Some(&self.0.long) }
69    }
70
71    pub fn slice(self, i: impl SliceIndex<[u8], Output = [u8]>) -> Self {
72        if let Some(long) = self.as_long() {
73            GermanBStr(long.slice(i))
74        } else {
75            GermanBStr(BStrInner {
76                short: ShortBStr::new(&(**self)[i]),
77            })
78        }
79    }
80}
81
82impl Deref for GermanBStr<'_> {
83    type Target = BStr;
84    fn deref(&self) -> &BStr {
85        self.as_ref()
86    }
87}
88
89impl AsRef<[u8]> for GermanBStr<'_> {
90    fn as_ref(&self) -> &[u8] {
91        if self.is_short() {
92            unsafe { &self.0.short }
93        } else {
94            unsafe { &self.0.long }
95        }
96    }
97}
98
99impl AsRef<BStr> for GermanBStr<'_> {
100    fn as_ref(&self) -> &BStr {
101        BStr::new(self)
102    }
103}
104
105impl Display for GermanBStr<'_> {
106    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
107        <BStr as Display>::fmt(&**self, f)
108    }
109}
110
111impl Debug for GermanBStr<'_> {
112    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113        let variant_label = if self.is_short() {
114            "Short"
115        } else {
116            match unsafe { self.0.long.tag() } {
117                Class::Borrowed => "Borrowed",
118                Class::Static => "Static",
119            }
120        };
121        f.debug_tuple(variant_label)
122            .field(&BStr::new(self))
123            .finish()
124    }
125}
126
127macro_rules! define_constructor {
128    ($name:ident,$ty:ty) => {
129        pub fn $name(data: $ty) -> Self {
130            if data.len() <= 12 {
131                GermanBStr(BStrInner {
132                    short: ShortBStr::new(data),
133                })
134            } else {
135                GermanBStr(BStrInner {
136                    long: LongBStr::$name(data),
137                })
138            }
139        }
140    };
141}
142
143impl<'a> GermanBStr<'a> {
144    define_constructor!(new_static, &'static [u8]);
145    define_constructor!(new_borrowed, &'a [u8]);
146
147    pub fn reallocate_borrowed<'b>(self, dst: impl StrAllocator<'b>) -> GermanBStr<'b> {
148        unsafe {
149            if self.is_short() {
150                GermanBStr(BStrInner {
151                    short: self.0.short,
152                })
153            } else {
154                GermanBStr(BStrInner {
155                    long: self.0.long.reallocate_borrowed(dst),
156                })
157            }
158        }
159    }
160}
161
162impl PartialOrd for GermanBStr<'_> {
163    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
164        Some(self.cmp(other))
165    }
166}
167
168impl Ord for GermanBStr<'_> {
169    fn cmp(&self, other: &Self) -> Ordering {
170        self.head_bytes()
171            .cmp(other.head_bytes())
172            .then_with(|| <[u8]>::cmp(self, other))
173    }
174}
175
176impl PartialEq for GermanBStr<'_> {
177    fn eq(&self, other: &Self) -> bool {
178        self.head() == other.head() && <[u8]>::eq(self, &**other)
179    }
180}
181
182impl Eq for GermanBStr<'_> {}
183
184impl Hash for GermanBStr<'_> {
185    fn hash<H: Hasher>(&self, state: &mut H) {
186        self.deref().hash(state)
187    }
188}
189
190#[cfg(feature = "bumpalo")]
191#[test]
192fn construct_eq() {
193    use ::bumpalo::Bump;
194    let static_str = b"abcdefghijklmnopqrstuvwxyzABCD";
195    let bump = Bump::new();
196    let bump2 = Bump::new();
197    let mut strings = Vec::new();
198    let mut strings2 = Vec::new();
199    for l in 0..30 {
200        let s = &static_str[..l];
201        let gb = GermanBStr::new_borrowed(s);
202        let gs = GermanBStr::new_static(s);
203        assert_eq!(gb, gs);
204        assert_eq!(*gb, s);
205        strings.push((gb.reallocate_borrowed(&bump), gs.reallocate_borrowed(&bump)));
206    }
207    for (b, s) in &strings {
208        assert_eq!(b, s);
209        strings2.push((b.reallocate_borrowed(&bump2), s.reallocate_borrowed(&bump2)));
210    }
211    drop(strings);
212    drop(bump);
213    for (b, s) in &strings2 {
214        assert_eq!(b, s);
215    }
216}
217
218#[test]
219fn substring() {
220    let static_str = b"abcdefghijklmnopqrstuvwxyzABCD";
221    for l in 0..20 {
222        let s = GermanBStr::new_static(&static_str[..l]);
223        for start in 0..=l {
224            for end in start..=l {
225                assert_eq!(
226                    s.slice(start..end),
227                    GermanBStr::new_static(&static_str[start..end])
228                );
229            }
230        }
231    }
232}