static_collections/string.rs
1// The static-string module
2
3use core::{fmt::{self, Debug, Display}, mem::MaybeUninit, ops::{AddAssign, Deref, DerefMut}, slice, str};
4
5use crate::ffi::c_str::strnlen;
6
7#[derive(Debug)]
8pub struct InsertError;
9
10/// The `StaticString` type is a fixed-capacity UTF-8 string object. \
11/// To estimate length `N` you need, consider the following UTF-8 facts:
12/// - 1-byte: English letters and basic punctuations.
13/// - 2-byte: Latin-based, Greek, Cyrillic, Hebrew, Armenian letters and Thai characters.
14/// - 3-byte: Chinese, Japanese and Korean characters.
15/// - 4-byte: Emoji and rare symbols.
16pub struct StaticString<const N:usize>
17{
18 used:usize,
19 buff:MaybeUninit<[u8;N]>
20}
21
22impl<const N:usize> Default for StaticString<N>
23{
24 fn default() -> Self
25 {
26 Self::new()
27 }
28}
29
30impl<const N:usize> StaticString<N>
31{
32 /// Creates a new empty `StaticString`.
33 ///
34 /// Given that the string is empty, the buffer that contains the string isn't initialized.
35 /// This means the initial operation is very inexpensive.
36 ///
37 /// # Examples
38 /// ```
39 /// use static_collections::string::StaticString;
40 /// let s:StaticString<512>=StaticString::new();
41 /// ```
42 pub const fn new()->Self
43 {
44 Self
45 {
46 used:0,
47 buff:MaybeUninit::uninit()
48 }
49 }
50
51 /// Returns a byte slice of this `StaticString`'s contents.
52 ///
53 /// # Examples
54 /// ```
55 /// use static_collections::string::StaticString;
56 /// let s:StaticString<64>=StaticString::from("Hello");
57 /// assert_eq!(s.as_bytes(),b"Hello");
58 /// ```
59 #[inline(always)] pub const fn as_bytes(&self)->&[u8]
60 {
61 unsafe
62 {
63 slice::from_raw_parts(self.buff.assume_init_ref().as_ptr(),self.used)
64 }
65 }
66
67 /// Returns a mutable byte slice of this `StaticString`'s contents.
68 ///
69 /// # Examples
70 /// ```
71 /// use static_collections::string::StaticString;
72 /// let mut s:StaticString<64>=StaticString::from("Hello");
73 /// let array=s.as_mut_bytes();
74 /// array[0]=b'C';
75 /// assert_eq!(s.as_bytes(),b"Cello");
76 /// ```
77 #[inline(always)] pub const fn as_mut_bytes(&mut self)->&mut [u8]
78 {
79 unsafe
80 {
81 slice::from_raw_parts_mut(self.buff.assume_init_mut().as_mut_ptr(),self.used)
82 }
83 }
84
85 /// Returns a string slice of this `StaticString`'s contents.
86 ///
87 /// # Examples
88 /// ```
89 /// use static_collections::string::StaticString;
90 /// let s:StaticString<64>=StaticString::from("Hello, World!");
91 /// assert_eq!(s.as_str(),"Hello, World!");
92 /// ```
93 #[inline(always)] pub const fn as_str(&self)->&str
94 {
95 unsafe
96 {
97 str::from_utf8_unchecked(self.as_bytes())
98 }
99 }
100
101 /// Returns a string slice of this `StaticString`'s contents.
102 ///
103 /// # Examples
104 /// ```
105 /// use static_collections::string::StaticString;
106 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
107 /// assert_eq!(s.as_mut_str(),"Hello, World!");
108 /// ```
109 #[inline(always)] pub const fn as_mut_str(&mut self)->&mut str
110 {
111 unsafe
112 {
113 str::from_utf8_unchecked_mut(self.as_mut_bytes())
114 }
115 }
116
117 /// Appends a given `char` to the end of this `StaticString`.
118 ///
119 /// # Examples
120 /// ```
121 /// use static_collections::string::StaticString;
122 /// let mut s:StaticString<64>=StaticString::from("Hello");
123 /// s.push('!');
124 /// assert_eq!(s.as_str(),"Hello!");
125 /// ```
126 pub fn push(&mut self,ch:char)->Result<(),InsertError>
127 {
128 let ch_len=ch.len_utf8();
129 if self.used+ch_len>N
130 {
131 Err(InsertError)
132 }
133 else
134 {
135 ch.encode_utf8(unsafe{&mut self.buff.assume_init_mut()[self.used..]});
136 self.used+=ch_len;
137 Ok(())
138 }
139 }
140
141 /// Appends a given string slice to the end of this `StaticString`.
142 ///
143 /// # Examples
144 /// ```
145 /// use static_collections::string::StaticString;
146 /// let mut s:StaticString<64>=StaticString::from("Hello");
147 /// s.push_str(", World!");
148 /// assert_eq!(s.as_str(),"Hello, World!");
149 /// ```
150 pub fn push_str(&mut self,string:&str)->Result<(),InsertError>
151 {
152 let str_len=string.len();
153 if self.used+str_len>N
154 {
155 Err(InsertError)
156 }
157 else {
158 let dest_buff=unsafe{&mut self.buff.assume_init_mut()[self.used..self.used+str_len]};
159 dest_buff.copy_from_slice(string.as_bytes());
160 self.used+=str_len;
161 Ok(())
162 }
163 }
164
165 /// Inserts a given `char` to the end of this `StaticString` at specified byte location `index`.
166 ///
167 /// # Examples
168 /// ```
169 /// use static_collections::string::StaticString;
170 /// let mut s:StaticString<64>=StaticString::from("Hello World!");
171 /// s.insert(5,',');
172 /// assert_eq!(s.as_str(),"Hello, World!");
173 /// ```
174 pub fn insert(&mut self,index:usize,ch:char)->Result<(),InsertError>
175 {
176 let ch_len=ch.len_utf8();
177 if self.used+ch_len>N
178 {
179 Err(InsertError)
180 }
181 else
182 {
183 // Move string contents.
184 let full_buff=unsafe{self.buff.assume_init_mut()};
185 full_buff.copy_within(index..self.used,index+ch_len);
186 ch.encode_utf8(&mut full_buff[index..index+ch_len]);
187 self.used+=ch_len;
188 Ok(())
189 }
190 }
191
192 /// Inserts a given string slice to the end of this `StaticString` at specified byte location `index`.
193 ///
194 /// # Examples
195 /// ```
196 /// use static_collections::string::StaticString;
197 /// let mut s:StaticString<64>=StaticString::from("Hello!");
198 /// s.insert_str(5,", World");
199 /// assert_eq!(s.as_str(),"Hello, World!");
200 /// ```
201 pub fn insert_str(&mut self,index:usize,string:&str)->Result<(),InsertError>
202 {
203 let str_len=string.len();
204 if self.used+str_len>N
205 {
206 Err(InsertError)
207 }
208 else
209 {
210 // Move string contents.
211 let full_buff=unsafe{self.buff.assume_init_mut()};
212 full_buff.copy_within(index..self.used,index+str_len);
213 full_buff[index..index+str_len].copy_from_slice(string.as_bytes());
214 self.used+=str_len;
215 Ok(())
216 }
217 }
218
219 /// Shortens this `StaticString` so that no null terminator is present in the string.
220 ///
221 /// # Example
222 /// ```
223 /// use static_collections::string::StaticString;
224 /// let mut s:StaticString<64>=StaticString::from("Hello,\0World!");
225 /// s.truncate_to_nul();
226 /// assert_eq!(s.as_str(),"Hello,");
227 /// ```
228 pub fn truncate_to_nul(&mut self)
229 {
230 self.used=unsafe{strnlen(self.as_bytes().as_ptr().cast(),self.len())};
231 }
232
233 /// Shortens this `StaticString` to the specified `new_len`.
234 ///
235 /// # Examples
236 /// ```
237 /// use static_collections::string::StaticString;
238 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
239 /// s.truncate(5);
240 /// assert_eq!(s.as_str(),"Hello");
241 /// ```
242 pub fn truncate(&mut self,new_len:usize)
243 {
244 if new_len<=self.used
245 {
246 self.used=new_len;
247 if str::from_utf8(self.as_bytes()).is_err()
248 {
249 panic!("The new length {new_len} does not lie on UTF-8 character boundary!");
250 }
251 }
252 }
253
254 /// Removes the last character from this `StaticString` and returns it. \
255 /// Returns `None` if this `StaticString` is empty.
256 ///
257 /// # Examples
258 /// ```
259 /// use static_collections::string::StaticString;
260 /// let mut s:StaticString<64>=StaticString::from("Hello!");
261 /// assert_eq!(s.pop(),Some('!'));
262 /// assert_eq!(s.as_str(),"Hello");
263 /// ```
264 pub fn pop(&mut self)->Option<char>
265 {
266 let s=self.as_str();
267 match s.chars().last()
268 {
269 Some(c)=>
270 {
271 self.used-=c.len_utf8();
272 Some(c)
273 }
274 None=>None
275 }
276 }
277
278 /// Removes the character from this `StaticString` specified at byte location and returns it.
279 ///
280 /// # Examples
281 /// ```
282 /// use static_collections::string::StaticString;
283 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
284 /// assert_eq!(s.remove(5),',');
285 /// assert_eq!(s.as_str(),"Hello World!");
286 /// ```
287 pub fn remove(&mut self,index:usize)->char
288 {
289 let full_buff=unsafe{self.buff.assume_init_mut()};
290 match str::from_utf8(&full_buff[index..self.used])
291 {
292 Ok(s)=>
293 {
294 let c=s.chars().nth(0).unwrap();
295 let ch_len=c.len_utf8();
296 full_buff.copy_within(index+ch_len..self.used,index);
297 self.used-=ch_len;
298 c
299 }
300 Err(_)=>panic!("Index {index} does not lie on UTF-8 character boundary!")
301 }
302 }
303
304 /// Returns the length of this string in bytes.
305 ///
306 /// # Examples
307 /// ```
308 /// use static_collections::string::StaticString;
309 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
310 /// assert_eq!(s.len(),13);
311 /// ```
312 #[inline(always)] pub fn len(&self)->usize
313 {
314 self.used
315 }
316
317 /// Returns the capacity of this string in bytes.
318 ///
319 /// # Examples
320 /// ```
321 /// use static_collections::string::StaticString;
322 /// let s:StaticString<128>=StaticString::new();
323 /// assert_eq!(s.capacity(),128);
324 /// ```
325 #[inline(always)] pub fn capacity(&self)->usize
326 {
327 N
328 }
329
330 /// Checks if this string is empty.
331 ///
332 /// # Examples
333 /// ```
334 /// use static_collections::string::StaticString;
335 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
336 /// assert_eq!(s.is_empty(),false);
337 /// s=StaticString::new();
338 /// assert_eq!(s.is_empty(),true);
339 /// ```
340 #[inline(always)] pub fn is_empty(&self)->bool
341 {
342 self.len()==0
343 }
344
345 /// Remove all contents of the string.
346 ///
347 /// # Examples
348 /// ```
349 /// use static_collections::string::StaticString;
350 /// let mut s:StaticString<64>=StaticString::from("Hello, World!");
351 /// assert_eq!(s.is_empty(),false);
352 /// s.clear();
353 /// assert_eq!(s.is_empty(),true);
354 /// ```
355 #[inline(always)] pub fn clear(&mut self)
356 {
357 self.used=0;
358 }
359}
360
361impl<const N:usize> Deref for StaticString<N>
362{
363 type Target = str;
364
365 fn deref(&self) -> &Self::Target
366 {
367 self.as_str()
368 }
369}
370
371impl<const N:usize> DerefMut for StaticString<N>
372{
373 fn deref_mut(&mut self) -> &mut Self::Target
374 {
375 self.as_mut_str()
376 }
377}
378
379impl<const N:usize> From<&str> for StaticString<N>
380{
381 fn from(value:&str)->Self
382 {
383 let mut s=Self::default();
384 if s.insert_str(0,value).is_err()
385 {
386 panic!("String is too large!");
387 }
388 s
389 }
390}
391
392impl<const N:usize> fmt::Write for StaticString<N>
393{
394 fn write_str(&mut self, s: &str) -> fmt::Result
395 {
396 match self.push_str(s)
397 {
398 Ok(())=>Ok(()),
399 Err(_)=>Err(fmt::Error)
400 }
401 }
402
403 fn write_char(&mut self, c: char) -> fmt::Result
404 {
405 match self.push(c)
406 {
407 Ok(_)=>Ok(()),
408 Err(_)=>Err(fmt::Error)
409 }
410 }
411}
412
413impl<const N:usize> Display for StaticString<N>
414{
415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
416 {
417 f.write_str(self.as_str())
418 }
419}
420
421impl<const N:usize> Debug for StaticString<N>
422{
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
424 {
425 f.write_str(self.as_str())
426 }
427}
428
429impl<const N:usize> AddAssign<&str> for StaticString<N>
430{
431 fn add_assign(&mut self, rhs: &str)
432 {
433 if self.push_str(rhs).is_err()
434 {
435 panic!("StaticString buffer Overflow!");
436 }
437 }
438}
439
440impl<const N:usize> PartialEq<&str> for StaticString<N>
441{
442 fn eq(&self,other:&&str)->bool
443 {
444 self.as_str().eq(*other)
445 }
446}
447
448/// This routine is the internal helper function for `format_static` macro. Do not use directly.
449pub fn _static_fmt_str<const N:usize>(args:fmt::Arguments)->Result<StaticString<N>,InsertError>
450{
451 let mut s:StaticString<N>=StaticString::new();
452 match fmt::write(&mut s,args)
453 {
454 Ok(_)=>Ok(s),
455 Err(_)=>Err(InsertError)
456 }
457}
458
459/// The `format_static` macro builds a static string via format.
460///
461/// # Example
462/// ```
463/// use static_collections::*;
464/// let s=format_static!(256,"Hello, {}!","World");
465/// assert_eq!(s.unwrap(),"Hello, World!");
466/// ```
467#[macro_export] macro_rules! format_static
468{
469 ($len:expr,$($arg:tt)*)=>
470 {
471 $crate::string::_static_fmt_str::<$len>(format_args!($($arg)*))
472 };
473}