1#![warn(missing_docs)]
8
9#![no_std]
10#![cfg_attr(feature = "cargo-clippy", 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
36#[derive(Copy, Clone)]
37pub struct StrBuf<const N: usize> {
84 inner: [mem::MaybeUninit<u8>; N],
85 cursor: u8, }
87
88impl<const N: usize> StrBuf<N> {
89 #[inline]
90 pub const fn new() -> Self {
92 unsafe {
93 Self::from_storage([mem::MaybeUninit::uninit(); N], 0)
94 }
95 }
96
97 #[inline]
98 pub const unsafe fn from_storage(storage: [mem::MaybeUninit<u8>; N], cursor: u8) -> Self {
103 debug_assert!(N <= u8::max_value() as usize, "Capacity cannot be more than 255");
104
105 Self {
106 inner: storage,
107 cursor,
108 }
109 }
110
111 #[inline]
112 pub const fn from_str(text: &str) -> Self {
114 let mut idx = 0;
115 let mut storage = [mem::MaybeUninit::<u8>::uninit(); N];
116
117 debug_assert!(text.len() <= storage.len(), "Text cannot fit static storage");
118 while idx < text.len() {
119 storage[idx] = mem::MaybeUninit::new(text.as_bytes()[idx]);
120 idx += 1;
121 }
122
123 unsafe {
124 Self::from_storage(storage, idx as u8)
125 }
126 }
127
128 #[inline]
129 pub const fn from_str_checked(text: &str) -> Result<Self, StrBufError> {
131 if text.len() <= Self::capacity() {
132 Ok(Self::from_str(text))
133 } else {
134 Err(StrBufError::Overflow)
135 }
136 }
137
138 #[inline(always)]
139 pub const unsafe fn get_unchecked(&self, idx: usize) -> u8 {
141 self.inner[idx].assume_init()
142 }
143
144 #[inline]
145 pub const fn get(&self, idx: usize) -> Option<u8> {
147 if idx < self.cursor as usize {
148 unsafe {
149 Some(self.get_unchecked(idx))
150 }
151 } else {
152 None
153 }
154 }
155
156 #[inline]
157 pub const fn as_ptr(&self) -> *const u8 {
159 self.inner.as_ptr() as _
160 }
161
162 #[inline]
163 pub fn as_mut_ptr(&mut self) -> *mut u8 {
165 self.inner.as_mut_ptr() as *mut u8
166 }
167
168 #[inline]
169 pub const fn remaining(&self) -> usize {
171 Self::capacity() - self.cursor as usize
172 }
173
174 #[inline]
175 pub const fn as_storage(&self) -> &[mem::MaybeUninit<u8>; N] {
177 &self.inner
178 }
179
180 #[inline]
181 pub unsafe fn as_mut_storage(&mut self) -> &mut [mem::MaybeUninit<u8>; N] {
185 &mut self.inner
186 }
187
188 #[inline]
189 pub const fn as_slice(&self) -> &[u8] {
191 #[repr(C)]
196 struct RawSlice {
197 ptr: *const u8,
198 size: usize,
199 }
200
201 debug_assert!(unsafe {
202 mem::transmute::<_, RawSlice>([3, 2, 1].as_slice()).size
203 } == 3, "RawSlice layout has been changed in compiler unexpectedly");
204
205 unsafe {
206 mem::transmute(RawSlice {
207 ptr: self.as_ptr(),
208 size: self.len(),
209 })
210 }
211 }
212
213 #[inline]
214 pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
218 slice::from_raw_parts_mut(self.as_mut_ptr(), self.cursor as usize)
219 }
220
221 #[inline]
222 pub fn as_write_slice(&mut self) -> &mut [mem::MaybeUninit<u8>] {
224 &mut self.inner[self.cursor as usize..]
225 }
226
227 #[inline(always)]
228 pub fn clear(&mut self) {
230 unsafe {
231 self.set_len(0);
232 }
233 }
234
235 #[inline(always)]
236 pub const fn empty(mut self) -> Self {
238 self.cursor = 0;
239 self
240 }
241
242 #[inline]
243 pub unsafe fn truncate(&mut self, cursor: u8) {
249 if cursor < self.cursor {
250 self.set_len(cursor);
251 }
252 }
253
254 #[inline]
255 pub const fn capacity() -> usize {
257 if N > u8::max_value() as usize {
258 u8::max_value() as usize
259 } else {
260 N
261 }
262 }
263
264 #[inline]
265 pub const fn len(&self) -> usize {
267 self.cursor as usize
268 }
269
270 #[inline(always)]
271 pub unsafe fn set_len(&mut self, len: u8) {
273 self.cursor = len
274 }
275
276 #[inline]
277 pub unsafe fn push_str_unchecked(&mut self, text: &str) {
279 ptr::copy_nonoverlapping(text.as_ptr(), self.as_mut_ptr().offset(self.cursor as isize), text.len());
280 self.set_len(self.cursor.saturating_add(text.len() as u8));
281 }
282
283 #[inline]
284 pub fn push_str(&mut self, text: &str) -> usize {
286 let mut size = cmp::min(text.len(), self.remaining());
287
288 #[cold]
289 fn shift_by_char_boundary(text: &str, mut size: usize) -> usize {
290 while !text.is_char_boundary(size) {
291 size -= 1;
292 }
293 size
294 }
295
296 if !text.is_char_boundary(size) {
297 size = shift_by_char_boundary(text, size - 1);
299 }
300
301 unsafe {
302 self.push_str_unchecked(&text[..size]);
303 }
304 size
305 }
306
307 #[inline]
308 pub const fn and(self, text: &str) -> Self {
312 unsafe {
313 self.and_unsafe(text.as_bytes())
314 }
315 }
316
317 #[inline]
318 pub const unsafe fn and_unsafe(mut self, bytes: &[u8]) -> Self {
322 debug_assert!(self.remaining() >= bytes.len(), "Buffer overflow");
323
324 let mut idx = 0;
325 while idx < bytes.len() {
326 let cursor = self.cursor as usize + idx;
327 self.inner[cursor] = mem::MaybeUninit::new(bytes[idx]);
328 idx += 1;
329 }
330 self.cursor = self.cursor.saturating_add(bytes.len() as u8);
331
332 self
333 }
334
335 #[inline(always)]
336 pub const fn as_str(&self) -> &str {
340 unsafe {
343 core::str::from_utf8_unchecked(self.as_slice())
344 }
345 }
346
347 #[inline(always)]
348 pub fn make_ascii_lowercase(&mut self) {
350 unsafe {
351 self.as_mut_slice().make_ascii_lowercase()
352 }
353 }
354
355 #[inline(always)]
356 pub fn make_ascii_uppercase(&mut self) {
358 unsafe {
359 self.as_mut_slice().make_ascii_uppercase()
360 }
361 }
362}
363
364impl<const S: usize> AsRef<str> for StrBuf<S> {
365 #[inline(always)]
366 fn as_ref(&self) -> &str {
367 self.as_str()
368 }
369}
370
371impl<const S: usize> core::fmt::Write for StrBuf<S> {
372 #[inline(always)]
373 fn write_str(&mut self, s: &str) -> core::fmt::Result {
374 if self.push_str(s) == s.len() {
375 Ok(())
376 } else {
377 Err(core::fmt::Error)
378 }
379 }
380}
381
382impl<const S: usize> core::fmt::Display for StrBuf<S> {
383 #[inline(always)]
384 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
385 f.write_str(self.as_str())
386 }
387}
388
389impl<const S: usize> core::fmt::Debug for StrBuf<S> {
390 #[inline(always)]
391 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
392 f.write_str(self.as_str())
393 }
394}
395
396impl<const S: usize> AsRef<[u8]> for StrBuf<S> {
397 #[inline(always)]
398 fn as_ref(&self) -> &[u8] {
399 self.as_slice()
400 }
401}
402
403impl<const S: usize> borrow::Borrow<str> for StrBuf<S> {
404 #[inline(always)]
405 fn borrow(&self) -> &str {
406 self.as_str()
407 }
408}
409
410impl<const S: usize> ops::Deref for StrBuf<S> {
411 type Target = str;
412
413 #[inline(always)]
414 fn deref(&self) -> &str {
415 self.as_str()
416 }
417}
418
419impl<const S: usize> Eq for StrBuf<S> {}
420
421impl<const S: usize> PartialEq<StrBuf<S>> for StrBuf<S> {
422 #[inline(always)]
423 fn eq(&self, other: &Self) -> bool {
424 self.as_str() == other.as_str()
425 }
426}
427
428impl<const S: usize> PartialEq<StrBuf<S>> for &str {
429 #[inline(always)]
430 fn eq(&self, other: &StrBuf<S>) -> bool {
431 *self == other.as_str()
432 }
433}
434
435impl<const S: usize> PartialEq<StrBuf<S>> for str {
436 #[inline(always)]
437 fn eq(&self, other: &StrBuf<S>) -> bool {
438 self == other.as_str()
439 }
440}
441
442impl<const S: usize> PartialEq<str> for StrBuf<S> {
443 #[inline(always)]
444 fn eq(&self, other: &str) -> bool {
445 self.as_str() == other
446 }
447}
448
449impl<const S: usize> PartialEq<&str> for StrBuf<S> {
450 #[inline(always)]
451 fn eq(&self, other: &&str) -> bool {
452 self.as_str() == *other
453 }
454}
455
456impl<const S: usize> cmp::Ord for StrBuf<S> {
457 fn cmp(&self, other: &Self) -> cmp::Ordering {
458 self.as_str().cmp(other.as_str())
459 }
460}
461
462impl<const S: usize> PartialOrd for StrBuf<S> {
463 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
464 Some(self.cmp(other))
465 }
466}
467
468impl<const S: usize> hash::Hash for StrBuf<S> {
469 fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
470 self.as_str().hash(hasher)
471 }
472}
473
474impl<const S: usize> core::convert::TryFrom<&str> for StrBuf<S> {
475 type Error = StrBufError;
476
477 #[inline(always)]
478 fn try_from(text: &str) -> Result<Self, Self::Error> {
479 Self::from_str_checked(text)
480 }
481}
482
483impl<const S: usize> core::str::FromStr for StrBuf<S> {
484 type Err = StrBufError;
485
486 #[inline(always)]
487 fn from_str(text: &str) -> Result<Self, Self::Err> {
488 Self::from_str_checked(text)
489 }
490}