static_collections/string.rs
1// The static-string module
2
3use core::{char::DecodeUtf16Error, fmt::{self, Debug, Display}, ops::{AddAssign, Deref, DerefMut}, str};
4
5use crate::{ffi::c_str::strnlen, vec::StaticVec};
6
7#[derive(Debug)]
8pub enum InsertError
9{
10 InsufficientSpace,
11 NonUtf8Boundary,
12 Utf16Error(DecodeUtf16Error)
13}
14
15/// The `StaticString` type is a fixed-capacity UTF-8 string object. \
16/// To estimate length `N` you need, consider the following UTF-8 facts:
17/// - 1-byte: English letters and basic punctuations.
18/// - 2-byte: Latin-based, Greek, Cyrillic, Hebrew, Armenian letters and Thai characters.
19/// - 3-byte: Chinese, Japanese and Korean characters.
20/// - 4-byte: Emoji and rare symbols.
21#[derive(Clone)]
22pub struct StaticString<const N:usize>
23{
24 internal:StaticVec<N,u8>
25}
26
27impl<const N:usize> Default for StaticString<N>
28{
29 fn default() -> Self
30 {
31 Self::new()
32 }
33}
34
35impl<const N:usize> StaticString<N>
36{
37 /// Creates a new empty `StaticString`.
38 ///
39 /// Given that the string is empty, the buffer that contains the string isn't initialized.
40 /// This means the initial operation is very inexpensive.
41 ///
42 /// # Examples
43 /// ```
44 /// use static_collections::string::StaticString;
45 /// let s:StaticString<512>=StaticString::new();
46 /// ```
47 pub const fn new()->Self
48 {
49 Self
50 {
51 internal:StaticVec::new()
52 }
53 }
54
55 /// Returns a byte slice of this `StaticString`'s contents.
56 ///
57 /// # Examples
58 /// ```
59 /// use static_collections::string::StaticString;
60 /// let s:StaticString<64>=StaticString::from("Hello");
61 /// assert_eq!(s.as_bytes(),b"Hello");
62 /// ```
63 #[inline(always)] pub const fn as_bytes(&self)->&[u8]
64 {
65 self.internal.as_slice()
66 }
67
68 /// Returns a mutable byte slice of this `StaticString`'s contents.
69 ///
70 /// # Examples
71 /// ```
72 /// use static_collections::string::StaticString;
73 /// let mut s:StaticString<64>=StaticString::from("Hello");
74 /// let array=s.as_mut_bytes();
75 /// array[0]=b'C';
76 /// assert_eq!(s.as_bytes(),b"Cello");
77 /// ```
78 #[inline(always)] pub const fn as_mut_bytes(&mut self)->&mut [u8]
79 {
80 self.internal.as_mut_slice()
81 }
82
83 /// Returns a string slice of this `StaticString`'s contents.
84 ///
85 /// # Examples
86 /// ```
87 /// use static_collections::string::StaticString;
88 /// let s:StaticString<64>=StaticString::from("Hello, World!");
89 /// assert_eq!(s.as_str(),"Hello, World!");
90 /// ```
91 #[inline(always)] pub const fn as_str(&self)->&str
92 {
93 unsafe
94 {
95 str::from_utf8_unchecked(self.as_bytes())
96 }
97 }
98
99 /// Returns a string slice of this `StaticString`'s contents.
100 ///
101 /// # Examples
102 /// ```
103 /// use static_collections::string::StaticString;
104 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
105 /// assert_eq!(s.as_mut_str(),"Hello, World!");
106 /// ```
107 #[inline(always)] pub const fn as_mut_str(&mut self)->&mut str
108 {
109 unsafe
110 {
111 str::from_utf8_unchecked_mut(self.as_mut_bytes())
112 }
113 }
114
115 /// Appends a given `char` to the end of this `StaticString`.
116 ///
117 /// # Examples
118 /// ```
119 /// use static_collections::string::StaticString;
120 /// let mut s:StaticString<64>=StaticString::from("Hello");
121 /// s.push('!');
122 /// assert_eq!(s.as_str(),"Hello!");
123 /// ```
124 pub fn push(&mut self,ch:char)->Result<(),InsertError>
125 {
126 let ch_len=ch.len_utf8();
127 let insertion_index=self.len();
128 if insertion_index+ch_len>N
129 {
130 Err(InsertError::InsufficientSpace)
131 }
132 else
133 {
134 unsafe
135 {
136 self.internal.force_resize(insertion_index+ch_len);
137 }
138 ch.encode_utf8(&mut self.internal[insertion_index..]);
139 Ok(())
140 }
141 }
142
143 /// Appends a given string slice to the end of this `StaticString`.
144 ///
145 /// # Examples
146 /// ```
147 /// use static_collections::string::StaticString;
148 /// let mut s:StaticString<64>=StaticString::from("Hello");
149 /// s.push_str(", World!");
150 /// assert_eq!(s.as_str(),"Hello, World!");
151 /// ```
152 pub fn push_str(&mut self,string:&str)->Result<(),InsertError>
153 {
154 let str_len=string.len();
155 let insertion_index=self.len();
156 if insertion_index+str_len>N
157 {
158 Err(InsertError::InsufficientSpace)
159 }
160 else
161 {
162 unsafe
163 {
164 self.internal.force_resize(insertion_index+str_len);
165 }
166 let dest_buff=&mut self.internal[insertion_index..];
167 dest_buff.copy_from_slice(string.as_bytes());
168 Ok(())
169 }
170 }
171
172 /// Decodes a native‑endian UTF‑16 encoded slice `v` into a `StaticString<N>`.
173 ///
174 /// Errors if the input contains invalid UTF‑16 **or** if the resulting
175 /// string would overflow the buffer capacity.
176 ///
177 /// # Examples
178 /// ```
179 /// use static_collections::string::*;
180 /// use utf16_lit::utf16;
181 /// let s: Result<StaticString<16>, InsertError>=StaticString::from_utf16(&utf16!("Hello, World!"));
182 /// assert_eq!(s.unwrap(),"Hello, World!");
183 /// // A surrogate alone is definitely not a valid character.
184 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16(&[0xd800]);
185 /// assert!(s.is_err());
186 /// ```
187 pub fn from_utf16(v:&[u16])->Result<Self,InsertError>
188 {
189 let mut r=Self::new();
190 for c in char::decode_utf16(v.iter().copied())
191 {
192 match c
193 {
194 Ok(ch)=>r.push(ch),
195 Err(e)=>return Err(InsertError::Utf16Error(e))
196 }?
197 }
198 Ok(r)
199 }
200
201 /// Decodes a little‑endian UTF‑16 slice `v` into a `StaticString<N>`.
202 ///
203 /// Behaviour and error conditions are the same as [`from_utf16`]: decoding
204 /// failures are wrapped in `InsertError::Utf16Error` and an overflow will
205 /// return `InsertError::InsufficientSpace`.
206 ///
207 /// # Examples
208 /// ```
209 /// use static_collections::string::*;
210 /// use utf16_lit::utf16;
211 /// let tmp:Vec<u16>=utf16!("Hello, World!").iter().map(|x| x.to_le()).collect();
212 /// let s: Result<StaticString<16>,InsertError>=StaticString::from_utf16le(tmp.as_slice());
213 /// assert_eq!(s.unwrap().as_str(),"Hello, World!");
214 /// // A surrogate alone is definitely not a valid character.
215 /// let tmp:u16=0xd800;
216 /// let s: Result<StaticString<16>,InsertError>=StaticString::from_utf16le(&[tmp.to_le()]);
217 /// assert!(s.is_err());
218 /// ```
219 pub fn from_utf16le(v:&[u16])->Result<Self,InsertError>
220 {
221 let mut r=Self::new();
222 for c in char::decode_utf16(v.iter().map(|x| x.to_le()))
223 {
224 match c
225 {
226 Ok(ch)=>r.push(ch),
227 Err(e)=>return Err(InsertError::Utf16Error(e))
228 }?
229 }
230 Ok(r)
231 }
232
233 /// Decodes a big‑endian UTF‑16 slice `v` into a `StaticString<N>`.
234 ///
235 /// Behaviour and error conditions are identical to [`from_utf16`]: decoding
236 /// failures are wrapped in `InsertError::Utf16Error` and an overflow will
237 /// return `InsertError::InsufficientSpace`.
238 ///
239 /// # Examples
240 /// ```
241 /// use static_collections::string::*;
242 /// use utf16_lit::utf16;
243 /// let tmp:Vec<u16>=utf16!("Hello, World!").iter().map(|x| x.to_be()).collect();
244 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16be(tmp.as_slice());
245 /// assert_eq!(s.unwrap().as_str(), "Hello, World!");
246 /// // A surrogate alone is definitely not a valid character.
247 /// let tmp:u16=0xd800;
248 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16be(&[tmp.to_be()]);
249 /// assert!(s.is_err());
250 /// ```
251 pub fn from_utf16be(v:&[u16])->Result<Self,InsertError>
252 {
253 let mut r=Self::new();
254 for c in char::decode_utf16(v.iter().map(|x| x.to_be()))
255 {
256 match c
257 {
258 Ok(ch)=>r.push(ch),
259 Err(e)=>return Err(InsertError::Utf16Error(e))
260 }?
261 }
262 Ok(r)
263 }
264
265 /// Decodes a native-endian UTF‑16 slice `v` into a `StaticString<N>`,
266 /// replacing any malformed sequences with [the replacement character (U+FFFD)](https://doc.rust-lang.org/core/char/constant.REPLACEMENT_CHARACTER.html).
267 ///
268 /// Unlike the previous implementation this variant also returns an error if
269 /// the output would not fit in the buffer.
270 ///
271 /// # Examples
272 /// ```
273 /// use static_collections::string::*;
274 /// use utf16_lit::utf16;
275 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16_lossy(&utf16!("Hello, World!"));
276 /// assert_eq!(s.unwrap().as_str(),"Hello, World!");
277 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16_lossy(&[0xd800]);
278 /// assert_eq!(s.unwrap().as_str(),String::from(char::REPLACEMENT_CHARACTER));
279 /// ```
280 pub fn from_utf16_lossy(v:&[u16])->Result<Self,InsertError>
281 {
282 let mut r=Self::new();
283 for c in char::decode_utf16(v.iter().copied())
284 {
285 let ch=match c
286 {
287 Ok(ch)=>ch,
288 Err(_)=>char::REPLACEMENT_CHARACTER
289 };
290 r.push(ch)?
291 }
292 Ok(r)
293 }
294
295 /// Decodes a little endian UTF-16-encoded vector `v` into this `StaticString<N>`,
296 /// replacing any malformed sequences with [the replacement character (U+FFFD)](https://doc.rust-lang.org/core/char/constant.REPLACEMENT_CHARACTER.html).
297 ///
298 /// # Examples
299 /// ```
300 /// use static_collections::string::*;
301 /// use utf16_lit::utf16;
302 /// let tmp:Vec<u16>=utf16!("Hello, World!").iter().map(|x| x.to_le()).collect();
303 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16le_lossy(tmp.as_slice());
304 /// assert_eq!(s.unwrap().as_str(),"Hello, World!");
305 /// // Lone surrogate
306 /// let tmp:u16=0xd800;
307 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16le_lossy(&[tmp.to_le()]);
308 /// assert_eq!(s.unwrap().as_str(), String::from(char::REPLACEMENT_CHARACTER));
309 /// ```
310 pub fn from_utf16le_lossy(v:&[u16])->Result<Self,InsertError>
311 {
312 let mut r=Self::new();
313 // If native endianness is LE, `to_le` does not invert bytes.
314 // If native endianness is BE, `to_le` will invert bytes.
315 for c in char::decode_utf16(v.iter().map(|x| x.to_le()))
316 {
317 let ch=match c
318 {
319 Ok(ch)=>ch,
320 Err(_)=>char::REPLACEMENT_CHARACTER
321 };
322 r.push(ch)?;
323 }
324 Ok(r)
325 }
326
327 /// Decodes a big endian UTF-16-encoded vector `v` into this `StaticString<N>`,
328 /// replacing any malformed sequences with [the replacement character (U+FFFD)](https://doc.rust-lang.org/core/char/constant.REPLACEMENT_CHARACTER.html).
329 ///
330 /// # Examples
331 /// ```
332 /// use static_collections::string::*;
333 /// use utf16_lit::utf16;
334 /// let tmp:Vec<u16>=utf16!("Hello, World!").iter().map(|x| x.to_be()).collect();
335 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16be_lossy(tmp.as_slice());
336 /// assert_eq!(s.unwrap().as_str(),"Hello, World!");
337 /// // Lone surrogate
338 /// let tmp:u16=0xd800;
339 /// let s:Result<StaticString<16>,InsertError>=StaticString::from_utf16be_lossy(&[tmp.to_be()]);
340 /// assert_eq!(s.unwrap().as_str(),String::from(char::REPLACEMENT_CHARACTER));
341 /// ```
342 pub fn from_utf16be_lossy(v:&[u16])->Result<Self,InsertError>
343 {
344 let mut r=Self::new();
345 // If native endianness is BE, `to_be` does not invert bytes.
346 // If native endianness is LE, `to_be` will invert bytes.
347 for c in char::decode_utf16(v.iter().map(|x| x.to_be()))
348 {
349 let ch=match c
350 {
351 Ok(ch)=>ch,
352 Err(_)=>char::REPLACEMENT_CHARACTER
353 };
354 r.push(ch)?;
355 }
356 Ok(r)
357 }
358
359 /// Inserts a given `char` to the end of this `StaticString` at specified byte location `index`.
360 ///
361 /// Returns `Err(InsertError)` if insertion failed:
362 /// - Insertion could fail if it overflows the capacity.
363 /// - Insertion could fail if `index` is in the middle of a character.
364 ///
365 /// Note that calling this in a loop can result in quadratic time complexity.
366 ///
367 /// # Examples
368 /// ```
369 /// use static_collections::string::StaticString;
370 /// let mut s:StaticString<64>=StaticString::from("Hello World!");
371 /// s.insert(5,',');
372 /// assert_eq!(s.as_str(),"Hello, World!");
373 /// ```
374 pub fn insert(&mut self,index:usize,ch:char)->Result<(),InsertError>
375 {
376 if !self.is_char_boundary(index)
377 {
378 return Err(InsertError::NonUtf8Boundary);
379 }
380 let ch_len=ch.len_utf8();
381 let old_end=self.len();
382 if old_end+ch_len>N
383 {
384 Err(InsertError::InsufficientSpace)
385 }
386 else
387 {
388 // Move string contents.
389 unsafe
390 {
391 self.internal.force_resize(old_end+ch_len);
392 }
393 self.internal.copy_within(index..old_end,index+ch_len);
394 ch.encode_utf8(&mut self.internal[index..index+ch_len]);
395 Ok(())
396 }
397 }
398
399 /// Inserts a given string slice to the end of this `StaticString` at specified byte location `index`.
400 ///
401 /// Returns `Err(InsertError)` if insertion failed:
402 /// - Insertion could fail if it overflows the capacity.
403 /// - Insertion could fail if `index` is in the middle of a character.
404 ///
405 /// Note that calling this in a loop can result in quadratic time complexity.
406 ///
407 /// # Examples
408 /// ```
409 /// use static_collections::string::StaticString;
410 /// let mut s:StaticString<64>=StaticString::from("Hello!");
411 /// s.insert_str(5,", World");
412 /// assert_eq!(s.as_str(),"Hello, World!");
413 /// ```
414 pub fn insert_str(&mut self,index:usize,string:&str)->Result<(),InsertError>
415 {
416 if !self.is_char_boundary(index)
417 {
418 return Err(InsertError::NonUtf8Boundary);
419 }
420 let str_len=string.len();
421 let old_end=self.len();
422 if old_end+str_len>N
423 {
424 Err(InsertError::InsufficientSpace)
425 }
426 else
427 {
428 // Move string contents.
429 unsafe
430 {
431 self.internal.force_resize(old_end+str_len);
432 }
433 self.internal.copy_within(index..old_end,index+str_len);
434 self.internal[index..index+str_len].copy_from_slice(string.as_bytes());
435 Ok(())
436 }
437 }
438
439 /// Shortens this `StaticString` so that no null terminator is present in the string.
440 ///
441 /// # Example
442 /// ```
443 /// use static_collections::string::StaticString;
444 /// let mut s:StaticString<64>=StaticString::from("Hello,\0World!");
445 /// s.truncate_to_nul();
446 /// assert_eq!(s.as_str(),"Hello,");
447 /// ```
448 pub fn truncate_to_nul(&mut self)
449 {
450 unsafe
451 {
452 let new_size=strnlen(self.as_bytes().as_ptr().cast(),self.len());
453 self.internal.force_resize(new_size);
454 };
455 }
456
457 /// Shortens this `StaticString` to the specified `new_len`.
458 ///
459 /// # Examples
460 /// ```
461 /// use static_collections::string::StaticString;
462 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
463 /// s.truncate(5);
464 /// assert_eq!(s.as_str(),"Hello");
465 /// ```
466 pub fn truncate(&mut self,new_len:usize)
467 {
468 if new_len<=self.len()
469 {
470 unsafe
471 {
472 self.internal.force_resize(new_len);
473 }
474 if str::from_utf8(self.as_bytes()).is_err()
475 {
476 panic!("The new length {new_len} does not lie on UTF-8 character boundary!");
477 }
478 }
479 }
480
481 /// Removes the last character from this `StaticString` and returns it. \
482 /// Returns `None` if this `StaticString` is empty.
483 ///
484 /// # Examples
485 /// ```
486 /// use static_collections::string::StaticString;
487 /// let mut s:StaticString<64>=StaticString::from("Hello!");
488 /// assert_eq!(s.pop(),Some('!'));
489 /// assert_eq!(s.as_str(),"Hello");
490 /// ```
491 pub fn pop(&mut self)->Option<char>
492 {
493 let s=self.as_str();
494 match s.chars().last()
495 {
496 Some(c)=>
497 {
498 let new_size=self.len()-c.len_utf8();
499 unsafe
500 {
501 self.internal.force_resize(new_size);
502 }
503 Some(c)
504 }
505 None=>None
506 }
507 }
508
509 /// Removes the character from this `StaticString` specified at byte location and returns it.
510 ///
511 /// # Examples
512 /// ```
513 /// use static_collections::string::StaticString;
514 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
515 /// assert_eq!(s.remove(5),',');
516 /// assert_eq!(s.as_str(),"Hello World!");
517 /// ```
518 pub fn remove(&mut self,index:usize)->char
519 {
520 match str::from_utf8(&self.internal[index..])
521 {
522 Ok(s)=>
523 {
524 let c=s.chars().nth(0).unwrap();
525 let ch_len=c.len_utf8();
526 self.internal.copy_within(index+ch_len..,index);
527 unsafe
528 {
529 self.internal.force_resize(self.len()-ch_len);
530 }
531 c
532 }
533 Err(_)=>panic!("Index {index} does not lie on UTF-8 character boundary!")
534 }
535 }
536
537 /// Returns the length of this string in bytes.
538 ///
539 /// # Examples
540 /// ```
541 /// use static_collections::string::StaticString;
542 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
543 /// assert_eq!(s.len(),13);
544 /// ```
545 #[inline(always)] pub fn len(&self)->usize
546 {
547 self.internal.len()
548 }
549
550 /// Returns the capacity of this string in bytes.
551 ///
552 /// # Examples
553 /// ```
554 /// use static_collections::string::StaticString;
555 /// let s:StaticString<128>=StaticString::new();
556 /// assert_eq!(s.capacity(),128);
557 /// ```
558 #[inline(always)] pub fn capacity(&self)->usize
559 {
560 N
561 }
562
563 /// Checks if this string is empty.
564 ///
565 /// # Examples
566 /// ```
567 /// use static_collections::string::StaticString;
568 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
569 /// assert_eq!(s.is_empty(),false);
570 /// s=StaticString::new();
571 /// assert_eq!(s.is_empty(),true);
572 /// ```
573 #[inline(always)] pub fn is_empty(&self)->bool
574 {
575 self.len()==0
576 }
577
578 /// Remove all contents of the string.
579 ///
580 /// # Examples
581 /// ```
582 /// use static_collections::string::StaticString;
583 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
584 /// assert_eq!(s.is_empty(),false);
585 /// s.clear();
586 /// assert_eq!(s.is_empty(),true);
587 /// ```
588 #[inline(always)] pub fn clear(&mut self)
589 {
590 self.internal.clear();
591 }
592}
593
594impl<const N:usize> Deref for StaticString<N>
595{
596 type Target = str;
597
598 fn deref(&self) -> &Self::Target
599 {
600 self.as_str()
601 }
602}
603
604impl<const N:usize> DerefMut for StaticString<N>
605{
606 fn deref_mut(&mut self) -> &mut Self::Target
607 {
608 self.as_mut_str()
609 }
610}
611
612impl<const N:usize> From<&str> for StaticString<N>
613{
614 fn from(value:&str)->Self
615 {
616 let mut s=Self::default();
617 if s.insert_str(0,value).is_err()
618 {
619 panic!("String is too large!");
620 }
621 s
622 }
623}
624
625impl<const N:usize> fmt::Write for StaticString<N>
626{
627 fn write_str(&mut self, s: &str) -> fmt::Result
628 {
629 match self.push_str(s)
630 {
631 Ok(())=>Ok(()),
632 Err(_)=>Err(fmt::Error)
633 }
634 }
635
636 fn write_char(&mut self, c: char) -> fmt::Result
637 {
638 match self.push(c)
639 {
640 Ok(_)=>Ok(()),
641 Err(_)=>Err(fmt::Error)
642 }
643 }
644}
645
646impl<const N:usize> Display for StaticString<N>
647{
648 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
649 {
650 f.write_str(self.as_str())
651 }
652}
653
654impl<const N:usize> Debug for StaticString<N>
655{
656 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
657 {
658 f.write_str(self.as_str())
659 }
660}
661
662impl<const N:usize> AddAssign<&str> for StaticString<N>
663{
664 fn add_assign(&mut self, rhs: &str)
665 {
666 if self.push_str(rhs).is_err()
667 {
668 panic!("StaticString buffer Overflow!");
669 }
670 }
671}
672
673impl<const N:usize> PartialEq<&str> for StaticString<N>
674{
675 fn eq(&self,other:&&str)->bool
676 {
677 self.as_str().eq(*other)
678 }
679}
680
681/// This routine is the internal helper function for `format_static` macro. Do not use directly.
682pub fn _static_fmt_str<const N:usize>(args:fmt::Arguments)->Result<StaticString<N>,InsertError>
683{
684 let mut s:StaticString<N>=StaticString::new();
685 match fmt::write(&mut s,args)
686 {
687 Ok(_)=>Ok(s),
688 Err(_)=>Err(InsertError::InsufficientSpace)
689 }
690}
691
692/// The `format_static` macro builds a static string via format.
693///
694/// # Example
695/// ```
696/// use static_collections::*;
697/// let s=format_static!(256,"Hello, {}!","World");
698/// assert_eq!(s.unwrap(),"Hello, World!");
699/// ```
700#[macro_export] macro_rules! format_static
701{
702 ($len:expr,$($arg:tt)*)=>
703 {
704 $crate::string::_static_fmt_str::<$len>(format_args!($($arg)*))
705 };
706}