1#![warn(missing_docs)]
8
9#![no_std]
10#![allow(clippy::style)]
11#![cfg_attr(rustfmt, rustfmt_skip)]
12
13use core::{mem, slice, ptr, cmp, ops, hash, fmt, borrow};
14
15#[cfg(feature = "serde")]
16mod serde;
17#[cfg(feature = "ufmt-write")]
18mod ufmt;
19
20#[derive(Debug, Clone)]
21pub enum StrBufError {
23 Overflow,
25}
26
27impl fmt::Display for StrBufError {
28 #[inline(always)]
29 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 StrBufError::Overflow => fmt.write_str("Buffer overflow"),
32 }
33 }
34}
35
36pub const CAPACITY_U8: usize = 256;
38pub const CAPACITY_U16: usize = 65537;
40
41pub const fn capacity(desired: usize) -> usize {
43 if desired == 0 {
44 desired
45 } else if desired <= u8::MAX as usize {
46 desired.saturating_add(1)
47 } else if desired <= u16::MAX as usize {
48 desired.saturating_add(2)
49 } else {
50 desired.saturating_add(mem::size_of::<usize>())
51 }
52}
53
54#[repr(transparent)]
55#[derive(Copy, Clone)]
56pub struct StrBuf<const N: usize> {
104 inner: [mem::MaybeUninit<u8>; N],
105}
106
107impl<const N: usize> StrBuf<N> {
108 pub const LEN_OFFSET: usize = if N == 0 {
110 0
111 } else if N <= CAPACITY_U8 {
112 1
113 } else if N <= CAPACITY_U16 {
114 2
115 } else {
116 mem::size_of::<usize>()
117 };
118
119 const CAPACITY: usize = N - Self::LEN_OFFSET;
120
121 #[inline]
122 pub const fn new() -> Self {
124 unsafe {
125 Self::from_storage([mem::MaybeUninit::uninit(); N]).const_set_len(0)
126 }
127 }
128
129 #[inline]
130 pub const unsafe fn from_storage(storage: [mem::MaybeUninit<u8>; N]) -> Self {
137 debug_assert!(N <= usize::max_value(), "Capacity cannot exceed usize");
138
139 Self {
140 inner: storage,
141 }
142 }
143
144 #[inline]
145 pub const fn from_str(text: &str) -> Self {
147 let mut idx = 0;
148 let mut storage = [mem::MaybeUninit::<u8>::uninit(); N];
149
150 debug_assert!(text.len() <= Self::CAPACITY, "Text cannot fit static storage");
151 while idx < text.len() {
152 storage[Self::LEN_OFFSET + idx] = mem::MaybeUninit::new(text.as_bytes()[idx]);
153 idx += 1;
154 }
155
156 unsafe {
157 Self::from_storage(storage).const_set_len(text.len())
158 }
159 }
160
161 #[inline]
162 pub const fn from_str_checked(text: &str) -> Result<Self, StrBufError> {
164 if text.len() <= Self::capacity() {
165 Ok(Self::from_str(text))
166 } else {
167 Err(StrBufError::Overflow)
168 }
169 }
170
171 #[inline(always)]
172 pub const unsafe fn get_unchecked(&self, idx: usize) -> u8 {
174 self.inner[Self::LEN_OFFSET + idx].assume_init()
175 }
176
177 #[inline]
178 pub const fn get(&self, idx: usize) -> Option<u8> {
180 if idx < self.len() {
181 unsafe {
182 Some(self.get_unchecked(idx))
183 }
184 } else {
185 None
186 }
187 }
188
189 #[inline]
190 pub const fn as_ptr(&self) -> *const u8 {
192 unsafe {
193 self.inner.as_ptr().add(Self::LEN_OFFSET) as _
194 }
195 }
196
197 #[inline]
198 pub fn as_mut_ptr(&mut self) -> *mut u8 {
200 unsafe {
201 self.inner.as_mut_ptr().add(Self::LEN_OFFSET) as *mut u8
202 }
203 }
204
205 #[inline]
206 pub const fn remaining(&self) -> usize {
208 Self::capacity() - self.len()
209 }
210
211 #[inline]
212 pub const fn as_storage(&self) -> &[mem::MaybeUninit<u8>; N] {
214 &self.inner
215 }
216
217 #[inline]
218 pub unsafe fn as_mut_storage(&mut self) -> &mut [mem::MaybeUninit<u8>; N] {
222 &mut self.inner
223 }
224
225 #[inline]
226 pub const fn as_slice(&self) -> &[u8] {
228 unsafe {
229 slice::from_raw_parts(self.as_ptr(), self.len())
230 }
231 }
232
233 #[inline]
234 pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
238 slice::from_raw_parts_mut(self.as_mut_ptr(), self.len())
239 }
240
241 #[inline]
242 pub fn as_write_slice(&mut self) -> &mut [mem::MaybeUninit<u8>] {
244 let len = self.len();
245 &mut self.inner[Self::LEN_OFFSET + len..]
246 }
247
248 #[inline(always)]
249 pub fn clear(&mut self) {
251 unsafe {
252 self.set_len(0);
253 }
254 }
255
256 #[inline(always)]
257 pub const fn empty(self) -> Self {
259 unsafe {
260 self.const_set_len(0)
261 }
262 }
263
264 #[inline]
265 pub unsafe fn truncate(&mut self, len: usize) {
271 if len < self.len() {
272 self.set_len(len);
273 }
274 }
275
276 #[inline]
277 pub const fn capacity() -> usize {
279 Self::CAPACITY
280 }
281
282 #[inline]
283 pub const fn len(&self) -> usize {
285 if N == 0 {
286 0
287 } else if N <= CAPACITY_U8 {
288 unsafe {
289 self.inner[0].assume_init() as _
290 }
291 } else if N <= CAPACITY_U16 {
292 unsafe {
293 u16::from_ne_bytes(*(self.inner.as_ptr() as *const [u8; mem::size_of::<u16>()])) as usize
294 }
295 } else {
296 unsafe {
297 usize::from_ne_bytes(*(self.inner.as_ptr() as *const [u8; mem::size_of::<usize>()]))
298 }
299 }
300 }
301
302 #[inline(always)]
303 pub const unsafe fn const_set_len(mut self, len: usize) -> Self {
305 if N == 0 {
306 } else if N <= CAPACITY_U8 {
308 self.inner[0] = mem::MaybeUninit::new(len as _);
309 } else if N <= CAPACITY_U16 {
310 let len = (len as u16).to_ne_bytes();
311 self.inner[0] = mem::MaybeUninit::new(len[0]);
312 self.inner[1] = mem::MaybeUninit::new(len[1]);
313 } else {
314 let len = len.to_ne_bytes();
315 let mut idx = 0;
316 loop {
317 self.inner[idx] = mem::MaybeUninit::new(len[idx]);
318 idx = idx.saturating_add(1);
319 if idx >= len.len() {
320 break;
321 }
322 }
323 }
324
325 self
326 }
327
328 #[inline(always)]
329 pub unsafe fn set_len(&mut self, len: usize) {
331 if N == 0 {
332 } else if N <= CAPACITY_U8 {
334 self.inner[0] = mem::MaybeUninit::new(len as _);
335 } else if N <= CAPACITY_U16 {
336 let len = (len as u16).to_ne_bytes();
337 self.inner[0] = mem::MaybeUninit::new(len[0]);
338 self.inner[1] = mem::MaybeUninit::new(len[1]);
339 } else {
340 let len = len.to_ne_bytes();
341 let ptr = self.inner.as_mut_ptr();
342 ptr::copy_nonoverlapping(len.as_ptr(), ptr as *mut _, mem::size_of::<usize>());
343 }
344 }
345
346 #[inline]
347 pub unsafe fn push_str_unchecked(&mut self, text: &str) {
349 ptr::copy_nonoverlapping(text.as_ptr(), self.as_mut_ptr().add(self.len()), text.len());
350 self.set_len(self.len().saturating_add(text.len()));
351 }
352
353 #[inline]
354 pub fn push_str(&mut self, text: &str) -> usize {
356 let mut size = cmp::min(text.len(), self.remaining());
357
358 #[cold]
359 fn shift_by_char_boundary(text: &str, mut size: usize) -> usize {
360 while !text.is_char_boundary(size) {
361 size -= 1;
362 }
363 size
364 }
365
366 if !text.is_char_boundary(size) {
367 size = shift_by_char_boundary(text, size - 1);
369 }
370
371 unsafe {
372 self.push_str_unchecked(&text[..size]);
373 }
374 size
375 }
376
377 #[inline]
378 pub const fn and(self, text: &str) -> Self {
382 unsafe {
383 self.and_unsafe(text.as_bytes())
384 }
385 }
386
387 #[inline]
388 pub const unsafe fn and_unsafe(mut self, bytes: &[u8]) -> Self {
392 debug_assert!(self.remaining() >= bytes.len(), "Buffer overflow");
393
394 let mut idx = 0;
395 let cursor = self.len();
396 while idx < bytes.len() {
397 self.inner[Self::LEN_OFFSET + cursor + idx] = mem::MaybeUninit::new(bytes[idx]);
398 idx += 1;
399 }
400 self.const_set_len(cursor + bytes.len())
401 }
402
403 #[inline(always)]
404 pub const fn as_str(&self) -> &str {
408 unsafe {
409 core::str::from_utf8_unchecked(self.as_slice())
410 }
411 }
412
413 #[inline(always)]
414 pub const fn into_ascii_lowercase(mut self) -> Self {
416 let len = Self::LEN_OFFSET + self.len();
417 let mut idx = Self::LEN_OFFSET;
418 loop {
419 if idx >= len {
420 break;
421 }
422
423 self.inner[idx] = unsafe {
424 mem::MaybeUninit::new(self.inner[idx].assume_init().to_ascii_lowercase())
425 };
426 idx = idx.saturating_add(1);
427 }
428 self
429 }
430
431 #[inline(always)]
432 pub fn make_ascii_lowercase(&mut self) {
434 unsafe {
435 self.as_mut_slice().make_ascii_lowercase()
436 }
437 }
438
439 #[inline(always)]
440 pub const fn into_ascii_uppercase(mut self) -> Self {
442 let len = Self::LEN_OFFSET + self.len();
443 let mut idx = Self::LEN_OFFSET;
444 loop {
445 if idx >= len {
446 break;
447 }
448
449 self.inner[idx] = unsafe {
450 mem::MaybeUninit::new(self.inner[idx].assume_init().to_ascii_uppercase())
451 };
452 idx = idx.saturating_add(1);
453 }
454 self
455 }
456
457 #[inline(always)]
458 pub fn make_ascii_uppercase(&mut self) {
460 unsafe {
461 self.as_mut_slice().make_ascii_uppercase()
462 }
463 }
464
465 pub fn make_trim(&mut self) {
467 let this = self.as_str();
468 let len = this.len();
469 let mut trim_left_count = 0usize;
470 let mut trim_right_count = 0usize;
471 let mut chars = this.chars();
472 while let Some(ch) = chars.next() {
473 if ch.is_whitespace() {
474 trim_left_count = trim_left_count.saturating_add(ch.len_utf8());
475 } else {
476 break;
477 }
478 }
479
480 for ch in chars.rev() {
481 if ch.is_whitespace() {
482 trim_right_count = trim_right_count.saturating_add(ch.len_utf8());
483 } else {
484 break;
485 }
486 }
487
488 let new_len = len.saturating_sub(trim_left_count).saturating_sub(trim_right_count);
489 if new_len != len {
490 unsafe {
491 let dest = self.as_mut_ptr();
493 let src = dest.add(trim_left_count) as *const _;
494 ptr::copy(src, dest, new_len);
495 self.set_len(new_len);
496 }
497 }
498 }
499
500 #[inline]
501 pub fn make_trim_left(&mut self) {
503 let this = self.as_str();
504 let len = this.len();
505 let mut trim_count = 0usize;
506 for ch in this.chars() {
507 if ch.is_whitespace() {
508 trim_count = trim_count.saturating_add(ch.len_utf8());
509 } else {
510 break;
511 }
512 }
513
514 let new_len = len.saturating_sub(trim_count);
515 unsafe {
516 let dest = self.as_mut_ptr();
517 let src = dest.add(trim_count);
518 ptr::copy(src, dest, new_len);
519 self.set_len(new_len);
520 }
521 }
522
523 #[inline]
524 pub fn make_trim_right(&mut self) {
526 let this = self.as_str();
527 let len = this.len();
528 let mut trim_count = 0usize;
529 for ch in this.chars().rev() {
530 if ch.is_whitespace() {
531 trim_count = trim_count.saturating_add(ch.len_utf8());
532 } else {
533 break;
534 }
535 }
536
537 unsafe {
538 self.set_len(len.saturating_sub(trim_count))
539 }
540 }
541
542 #[inline]
543 pub fn pop(&mut self) -> Option<char> {
545 let ch = self.chars().rev().next()?;
546 let new_len = self.len() - ch.len_utf8();
547 unsafe {
548 self.set_len(new_len)
549 }
550 Some(ch)
551 }
552}
553
554impl<const S: usize> AsRef<str> for StrBuf<S> {
555 #[inline(always)]
556 fn as_ref(&self) -> &str {
557 self.as_str()
558 }
559}
560
561impl<const S: usize> fmt::Write for StrBuf<S> {
562 #[inline(always)]
563 fn write_str(&mut self, s: &str) -> fmt::Result {
564 if self.push_str(s) == s.len() {
565 Ok(())
566 } else {
567 Err(fmt::Error)
568 }
569 }
570}
571
572impl<const S: usize> fmt::Display for StrBuf<S> {
573 #[inline(always)]
574 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
575 fmt::Display::fmt(self.as_str(), fmt)
576 }
577}
578
579impl<const S: usize> fmt::Debug for StrBuf<S> {
580 #[inline(always)]
581 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
582 fmt::Debug::fmt(self.as_str(), fmt)
583 }
584}
585
586impl<const S: usize> AsRef<[u8]> for StrBuf<S> {
587 #[inline(always)]
588 fn as_ref(&self) -> &[u8] {
589 self.as_slice()
590 }
591}
592
593impl<const S: usize> borrow::Borrow<str> for StrBuf<S> {
594 #[inline(always)]
595 fn borrow(&self) -> &str {
596 self.as_str()
597 }
598}
599
600impl<const S: usize> ops::Deref for StrBuf<S> {
601 type Target = str;
602
603 #[inline(always)]
604 fn deref(&self) -> &str {
605 self.as_str()
606 }
607}
608
609impl<const S: usize> Eq for StrBuf<S> {}
610
611impl<const S: usize> PartialEq<StrBuf<S>> for StrBuf<S> {
612 #[inline(always)]
613 fn eq(&self, other: &Self) -> bool {
614 self.as_str() == other.as_str()
615 }
616}
617
618impl<const S: usize> PartialEq<StrBuf<S>> for &str {
619 #[inline(always)]
620 fn eq(&self, other: &StrBuf<S>) -> bool {
621 *self == other.as_str()
622 }
623}
624
625impl<const S: usize> PartialEq<StrBuf<S>> for str {
626 #[inline(always)]
627 fn eq(&self, other: &StrBuf<S>) -> bool {
628 self == other.as_str()
629 }
630}
631
632impl<const S: usize> PartialEq<str> for StrBuf<S> {
633 #[inline(always)]
634 fn eq(&self, other: &str) -> bool {
635 self.as_str() == other
636 }
637}
638
639impl<const S: usize> PartialEq<&str> for StrBuf<S> {
640 #[inline(always)]
641 fn eq(&self, other: &&str) -> bool {
642 self.as_str() == *other
643 }
644}
645
646impl<const S: usize> cmp::Ord for StrBuf<S> {
647 fn cmp(&self, other: &Self) -> cmp::Ordering {
648 self.as_str().cmp(other.as_str())
649 }
650}
651
652impl<const S: usize> PartialOrd for StrBuf<S> {
653 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
654 Some(self.cmp(other))
655 }
656}
657
658impl<const S: usize> hash::Hash for StrBuf<S> {
659 fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
660 self.as_str().hash(hasher)
661 }
662}
663
664impl<const S: usize> core::convert::TryFrom<&str> for StrBuf<S> {
665 type Error = StrBufError;
666
667 #[inline(always)]
668 fn try_from(text: &str) -> Result<Self, Self::Error> {
669 Self::from_str_checked(text)
670 }
671}
672
673impl<const S: usize> core::str::FromStr for StrBuf<S> {
674 type Err = StrBufError;
675
676 #[inline(always)]
677 fn from_str(text: &str) -> Result<Self, Self::Err> {
678 Self::from_str_checked(text)
679 }
680}