Skip to main content

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}