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