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}