1use crate::*;
2use std::{char, fmt::Debug, hash::Hash, mem::MaybeUninit};
3
4const MAX_STR_SIZE: usize = 256;
5
6#[derive(Clone, Copy)]
8pub struct ConstStr {
9 bytes: [MaybeUninit<u8>; MAX_STR_SIZE],
10 len: u32,
11}
12
13impl Debug for ConstStr {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 f.debug_struct("ConstStr")
16 .field("str", &self.as_str())
17 .finish()
18 }
19}
20
21#[cfg(feature = "serde")]
22mod serde_bytes {
23 use serde::{Deserialize, Serialize, Serializer};
24
25 use crate::ConstStr;
26
27 impl Serialize for ConstStr {
28 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
29 where
30 S: Serializer,
31 {
32 serializer.serialize_str(self.as_str())
33 }
34 }
35
36 impl<'de> Deserialize<'de> for ConstStr {
37 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38 where
39 D: serde::Deserializer<'de>,
40 {
41 let s = String::deserialize(deserializer)?;
42 Ok(ConstStr::new(&s))
43 }
44 }
45}
46
47unsafe impl SerializeConst for ConstStr {
48 const MEMORY_LAYOUT: Layout = Layout::List(ListLayout::new(
49 std::mem::size_of::<Self>(),
50 std::mem::offset_of!(Self, len),
51 PrimitiveLayout {
52 size: std::mem::size_of::<u32>(),
53 },
54 std::mem::offset_of!(Self, bytes),
55 ArrayLayout {
56 len: MAX_STR_SIZE,
57 item_layout: &Layout::Primitive(PrimitiveLayout {
58 size: std::mem::size_of::<u8>(),
59 }),
60 },
61 ));
62}
63
64impl ConstStr {
65 pub const fn new(s: &str) -> Self {
67 let str_bytes = s.as_bytes();
68 let mut bytes = [MaybeUninit::uninit(); MAX_STR_SIZE];
69 let mut i = 0;
70 while i < str_bytes.len() {
71 bytes[i] = MaybeUninit::new(str_bytes[i]);
72 i += 1;
73 }
74 Self {
75 bytes,
76 len: str_bytes.len() as u32,
77 }
78 }
79
80 const fn bytes(&self) -> &[u8] {
82 unsafe {
84 &*(self.bytes.split_at(self.len as usize).0 as *const [MaybeUninit<u8>]
85 as *const [u8])
86 }
87 }
88
89 pub const fn as_str(&self) -> &str {
91 let str_bytes = self.bytes();
92 match std::str::from_utf8(str_bytes) {
93 Ok(s) => s,
94 Err(_) => panic!(
95 "Invalid utf8; ConstStr should only ever be constructed from valid utf8 strings"
96 ),
97 }
98 }
99
100 pub const fn len(&self) -> usize {
102 self.len as usize
103 }
104
105 pub const fn is_empty(&self) -> bool {
107 self.len == 0
108 }
109
110 pub const fn push(self, byte: char) -> Self {
112 assert!(byte.is_ascii(), "Only ASCII bytes are supported");
113 let (bytes, len) = char_to_bytes(byte);
114 let (str, _) = bytes.split_at(len);
115 let Ok(str) = std::str::from_utf8(str) else {
116 panic!("Invalid utf8; char_to_bytes should always return valid utf8 bytes")
117 };
118 self.push_str(str)
119 }
120
121 pub const fn push_str(self, str: &str) -> Self {
123 let Self { mut bytes, len } = self;
124 assert!(
125 str.len() + len as usize <= MAX_STR_SIZE,
126 "String is too long"
127 );
128 let str_bytes = str.as_bytes();
129 let new_len = len as usize + str_bytes.len();
130 let mut i = 0;
131 while i < str_bytes.len() {
132 bytes[len as usize + i] = MaybeUninit::new(str_bytes[i]);
133 i += 1;
134 }
135 Self {
136 bytes,
137 len: new_len as u32,
138 }
139 }
140
141 pub const fn split_at(self, index: usize) -> (Self, Self) {
143 let (left, right) = self.bytes().split_at(index);
144 let left = match std::str::from_utf8(left) {
145 Ok(s) => s,
146 Err(_) => {
147 panic!("Invalid utf8; you cannot split at a byte that is not a char boundary")
148 }
149 };
150 let right = match std::str::from_utf8(right) {
151 Ok(s) => s,
152 Err(_) => {
153 panic!("Invalid utf8; you cannot split at a byte that is not a char boundary")
154 }
155 };
156 (Self::new(left), Self::new(right))
157 }
158
159 pub const fn rsplit_once(&self, char: char) -> Option<(Self, Self)> {
161 let str = self.as_str();
162 let mut index = str.len() - 1;
163 let (char_bytes, len) = char_to_bytes(char);
165 let (char_bytes, _) = char_bytes.split_at(len);
166 let bytes = str.as_bytes();
167
168 loop {
170 let byte = bytes[index];
171 if let Some(char_boundary_len) = utf8_char_boundary_to_char_len(byte) {
173 let (before_char, after_index) = bytes.split_at(index);
175 let (in_char, after_char) = after_index.split_at(char_boundary_len as usize);
176 if in_char.len() != char_boundary_len as usize {
177 panic!("in_char.len() should always be equal to char_boundary_len as usize")
178 }
179 let mut in_char_eq = true;
181 let mut i = 0;
182 let min_len = if in_char.len() < char_bytes.len() {
183 in_char.len()
184 } else {
185 char_bytes.len()
186 };
187 while i < min_len {
188 in_char_eq &= in_char[i] == char_bytes[i];
189 i += 1;
190 }
191 if in_char_eq {
193 let Ok(before_char_str) = std::str::from_utf8(before_char) else {
194 panic!(
195 "Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary"
196 )
197 };
198 let Ok(after_char_str) = std::str::from_utf8(after_char) else {
199 panic!(
200 "Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary"
201 )
202 };
203 return Some((Self::new(before_char_str), Self::new(after_char_str)));
204 }
205 }
206 match index.checked_sub(1) {
207 Some(new_index) => index = new_index,
208 None => return None,
209 }
210 }
211 }
212
213 pub const fn split_once(&self, char: char) -> Option<(Self, Self)> {
215 let str = self.as_str();
216 let mut index = 0;
217 let (char_bytes, len) = char_to_bytes(char);
219 let (char_bytes, _) = char_bytes.split_at(len);
220 let bytes = str.as_bytes();
221
222 while index < bytes.len() {
224 let byte = bytes[index];
225 if let Some(char_boundary_len) = utf8_char_boundary_to_char_len(byte) {
227 let (before_char, after_index) = bytes.split_at(index);
229 let (in_char, after_char) = after_index.split_at(char_boundary_len as usize);
230 if in_char.len() != char_boundary_len as usize {
231 panic!("in_char.len() should always be equal to char_boundary_len as usize")
232 }
233 let mut in_char_eq = true;
235 let mut i = 0;
236 let min_len = if in_char.len() < char_bytes.len() {
237 in_char.len()
238 } else {
239 char_bytes.len()
240 };
241 while i < min_len {
242 in_char_eq &= in_char[i] == char_bytes[i];
243 i += 1;
244 }
245 if in_char_eq {
247 let Ok(before_char_str) = std::str::from_utf8(before_char) else {
248 panic!(
249 "Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary"
250 )
251 };
252 let Ok(after_char_str) = std::str::from_utf8(after_char) else {
253 panic!(
254 "Invalid utf8; utf8_char_boundary_to_char_len should only return Some when the byte is a character boundary"
255 )
256 };
257 return Some((Self::new(before_char_str), Self::new(after_char_str)));
258 }
259 }
260 index += 1
261 }
262 None
263 }
264}
265
266impl PartialEq for ConstStr {
267 fn eq(&self, other: &Self) -> bool {
268 self.as_str() == other.as_str()
269 }
270}
271
272impl Eq for ConstStr {}
273
274impl PartialOrd for ConstStr {
275 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
276 Some(self.cmp(other))
277 }
278}
279
280impl Ord for ConstStr {
281 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
282 self.as_str().cmp(other.as_str())
283 }
284}
285
286impl Hash for ConstStr {
287 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
288 self.as_str().hash(state);
289 }
290}
291
292#[test]
293fn test_rsplit_once() {
294 let str = ConstStr::new("hello world");
295 assert_eq!(
296 str.rsplit_once(' '),
297 Some((ConstStr::new("hello"), ConstStr::new("world")))
298 );
299
300 let unicode_str = ConstStr::new("hi😀hello😀world😀world");
301 assert_eq!(
302 unicode_str.rsplit_once('😀'),
303 Some((ConstStr::new("hi😀hello😀world"), ConstStr::new("world")))
304 );
305 assert_eq!(unicode_str.rsplit_once('❌'), None);
306
307 for _ in 0..100 {
308 let random_str: String = (0..rand::random::<u8>() % 50)
309 .map(|_| rand::random::<char>())
310 .collect();
311 let konst = ConstStr::new(&random_str);
312 let mut seen_chars = std::collections::HashSet::new();
313 for char in random_str.chars().rev() {
314 let (char_bytes, len) = char_to_bytes(char);
315 let char_bytes = &char_bytes[..len];
316 assert_eq!(char_bytes, char.to_string().as_bytes());
317 if seen_chars.contains(&char) {
318 continue;
319 }
320 seen_chars.insert(char);
321 let (correct_left, correct_right) = random_str.rsplit_once(char).unwrap();
322 let (left, right) = konst.rsplit_once(char).unwrap();
323 println!("splitting {random_str:?} at {char:?}");
324 assert_eq!(left.as_str(), correct_left);
325 assert_eq!(right.as_str(), correct_right);
326 }
327 }
328}
329
330const CONTINUED_CHAR_MASK: u8 = 0b10000000;
331const BYTE_CHAR_BOUNDARIES: [u8; 4] = [0b00000000, 0b11000000, 0b11100000, 0b11110000];
332
333const fn char_to_bytes(char: char) -> ([u8; 4], usize) {
335 let code = char as u32;
336 let len = char.len_utf8();
337 let mut bytes = [0; 4];
338 match len {
339 1 => {
340 bytes[0] = code as u8;
341 }
342 2 => {
343 bytes[0] = ((code >> 6) & 0x1F) as u8 | BYTE_CHAR_BOUNDARIES[1];
344 bytes[1] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;
345 }
346 3 => {
347 bytes[0] = ((code >> 12) & 0x0F) as u8 | BYTE_CHAR_BOUNDARIES[2];
348 bytes[1] = ((code >> 6) & 0x3F) as u8 | CONTINUED_CHAR_MASK;
349 bytes[2] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;
350 }
351 4 => {
352 bytes[0] = ((code >> 18) & 0x07) as u8 | BYTE_CHAR_BOUNDARIES[3];
353 bytes[1] = ((code >> 12) & 0x3F) as u8 | CONTINUED_CHAR_MASK;
354 bytes[2] = ((code >> 6) & 0x3F) as u8 | CONTINUED_CHAR_MASK;
355 bytes[3] = (code & 0x3F) as u8 | CONTINUED_CHAR_MASK;
356 }
357 _ => panic!(
358 "encode_utf8: need more than 4 bytes to encode the unicode character, but the buffer has 4 bytes"
359 ),
360 };
361 (bytes, len)
362}
363
364#[test]
365fn fuzz_char_to_bytes() {
366 use std::char;
367 for _ in 0..100 {
368 let char = rand::random::<char>();
369 let (bytes, len) = char_to_bytes(char);
370 let str = std::str::from_utf8(&bytes[..len]).unwrap();
371 assert_eq!(char.to_string(), str);
372 }
373}
374
375const fn utf8_char_boundary_to_char_len(byte: u8) -> Option<u8> {
376 match byte {
377 0b00000000..=0b01111111 => Some(1),
378 0b11000000..=0b11011111 => Some(2),
379 0b11100000..=0b11101111 => Some(3),
380 0b11110000..=0b11111111 => Some(4),
381 _ => None,
382 }
383}
384
385#[test]
386fn fuzz_utf8_byte_to_char_len() {
387 for _ in 0..100 {
388 let random_string: String = (0..rand::random::<u8>())
389 .map(|_| rand::random::<char>())
390 .collect();
391 let bytes = random_string.as_bytes();
392 let chars: std::collections::HashMap<_, _> = random_string.char_indices().collect();
393 for (i, byte) in bytes.iter().enumerate() {
394 match utf8_char_boundary_to_char_len(*byte) {
395 Some(char_len) => {
396 let char = chars
397 .get(&i)
398 .unwrap_or_else(|| panic!("{byte:b} is not a character boundary"));
399 assert_eq!(char.len_utf8(), char_len as usize);
400 }
401 None => {
402 assert!(!chars.contains_key(&i), "{byte:b} is a character boundary");
403 }
404 }
405 }
406 }
407}