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}