token_string/
builder.rs

1// SPDX-FileCopyrightText: Copyright (C) 2024 Roland Csaszar
2// SPDX-License-Identifier: MPL-2.0
3//
4// Project:  token-string
5// File:     builder.rs
6// Date:     22.Nov.2024
7// =============================================================================
8//! String builder, concatenation of [`TokenString`]s.
9
10use crate::{
11	EMPTY,
12	MAX_LENGTH,
13	MAX_LENGTH_SMALL,
14	MAX_LENGTH_SMALL_ADD1,
15	PREFIX_LENGTH,
16	StringPtr,
17	TkStrError,
18	TokenString,
19};
20
21extern crate alloc;
22
23use core::{fmt, marker, mem, ptr, slice};
24
25/// A struct to concatenate [`TokenString`]s without allocating unnecessary
26/// memory on the heap.
27///
28/// The const generic parameter `N` should be set to the number of strings to
29/// concatenate. See the [`crate::concat!`] macro for a more comfortable way to
30/// use a [`Builder`].
31///
32/// Memory:
33///
34/// Uses a static array of `N`  pointers to [`TokenString`]s and two 8 byte
35/// fields, so (N + 2) * 8 bytes, (N + 2) * 64 bits.
36#[derive(Debug, Clone, Copy)]
37pub struct Builder<'a, const N: usize> {
38	/// The array of strings to concatenate.
39	strings: [*const TokenString; N],
40	/// The size of the concatenated string.
41	total_size: usize,
42	/// The number of strings to concatenate.
43	num_strings: usize,
44	/// Phantom field to hold the lifetime of the [`TokenString`] pointers.
45	lifetime: marker::PhantomData<&'a ()>,
46}
47
48/// An iterator over the [`TokenString`]s of a [`Builder`].
49///
50/// A way to iterator over the strings to concatenate in a [`Builder`].
51#[derive(Debug)]
52pub struct BuilderIter<'a, const N: usize> {
53	/// The [`Builder`] to iterate over.
54	builder: &'a Builder<'a, N>,
55	/// The current index in the array of [`TokenString`]s to concatenate.
56	idx: usize,
57}
58
59impl<const N: usize> fmt::Display for Builder<'_, N> {
60	#[inline]
61	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62		write!(f, "Builder < ")?;
63		for idx in 0 .. self.num_strings {
64			if idx == 0 {
65				// SAFETY:
66				// The pointers have been constructed from valid references, and
67				// are not null.
68				write!(f, "'{}'", unsafe { &*self.strings[idx] })?;
69			} else {
70				// SAFETY:
71				// The pointers have been constructed from valid references, and
72				// are not null.
73				write!(f, " + '{}'", unsafe { &*self.strings[idx] })?;
74			}
75		}
76		write!(f, " >")
77	}
78}
79
80impl<'a, const N: usize> IntoIterator for &'a Builder<'a, N> {
81	type IntoIter = BuilderIter<'a, N>;
82	type Item = &'a TokenString;
83
84	#[inline]
85	fn into_iter(self) -> Self::IntoIter {
86		Self::IntoIter {
87			builder: self,
88			idx: 0,
89		}
90	}
91}
92
93impl<'a, const N: usize> Iterator for BuilderIter<'a, N> {
94	type Item = &'a TokenString;
95
96	#[inline]
97	fn next(&mut self) -> Option<Self::Item> {
98		debug_assert!(
99			self.idx <= self.builder.num_strings,
100			"Builder iterator index is out of bounds"
101		);
102		self.idx += 1;
103		if self.idx - 1 == self.builder.num_strings {
104			None
105		} else {
106			// SAFETY:
107			// The pointers have been constructed from valid references, and
108			// are not null.
109			Some(unsafe { &*self.builder.strings[self.idx - 1] })
110		}
111	}
112}
113
114impl<'a, const N: usize> Builder<'a, N> {
115	#[inline]
116	#[must_use]
117	pub fn iter(&'a self) -> BuilderIter<'a, N> {
118		<&Self as IntoIterator>::into_iter(self)
119	}
120
121	/// Create a new [`Builder`] from the given [`TokenString`].
122	///
123	/// Use this to concatenate the given string with other strings.
124	///
125	/// # Panics
126	///
127	/// Panics, if the number of strings to concatenate is set to 0.
128	#[inline]
129	#[must_use]
130	pub const fn new(s: &'a TokenString) -> Self {
131		assert!(N > 0, "the number of elements must not be 0");
132		let mut ret_val = Self {
133			total_size: s.len as usize,
134			strings: [ptr::null(); N],
135			num_strings: 1,
136			lifetime: marker::PhantomData,
137		};
138		ret_val.strings[0] = s;
139		ret_val
140	}
141
142	/// Concatenate the given string at the end of the builder.
143	///
144	/// See also [`Builder::concat_checked`] for the version that checks for
145	/// errors, or the trait method [`Concat::concat`].
146	///
147	/// # Panics
148	///
149	/// Panics, if the number of concatenated strings is bigger than the size
150	/// reserved by the constant generic `N` of [`Builder`].
151	#[inline]
152	#[must_use]
153	pub const fn concat_unchecked(&mut self, s: &'a TokenString) -> &mut Self {
154		assert!(
155			(self.num_strings < N),
156			"more strings concatenated than reserved space in Builder"
157		);
158		self.total_size += s.len as usize;
159		self.strings[self.num_strings] = s;
160		self.num_strings += 1;
161
162		self
163	}
164
165	/// Concatenate another string to the end of the builder.
166	///
167	/// See also [`Builder::concat_unchecked`] for the version that does not
168	/// check for errors, or the trait method [`concat`].
169	///
170	/// # Errors
171	///
172	/// This function will return [`TkStrError::TooBig`] if the concatenated
173	/// string is greater than [`MAX_LENGTH`].
174	/// If more than `N`, the const generic value of the [`Builder`], strings
175	/// are being concatenated, [`TkStrError::TooMany`] is returned.
176	#[inline]
177	pub const fn concat_checked(
178		&mut self,
179		s: &'a TokenString,
180	) -> Result<&mut Self, TkStrError> {
181		if self.num_strings == N {
182			return Err(TkStrError::TooMany(N));
183		}
184		if self.total_size + s.len as usize > MAX_LENGTH {
185			return Err(TkStrError::TooBig(self.total_size + s.len as usize));
186		}
187		self.total_size += s.len as usize;
188		self.strings[self.num_strings] = s;
189		self.num_strings += 1;
190
191		Ok(self)
192	}
193
194	/// Collect all strings passed to this [`Builder`].
195	///
196	/// # Errors
197	///
198	/// Returns [`TkStrError::TooBig`] if the sum of all string lengths is
199	/// greater than [`MAX_LENGTH`].
200	#[inline]
201	pub fn collect_checked(self) -> Result<TokenString, TkStrError> {
202		match self.total_size {
203			| 0 => Ok(EMPTY),
204			| 1 ..= MAX_LENGTH_SMALL => Ok(self.collect_to_small()),
205			| MAX_LENGTH_SMALL_ADD1 ..= MAX_LENGTH =>
206				Ok(self.collect_to_alloc()),
207			| _ => Err(TkStrError::TooBig(self.total_size)),
208		}
209	}
210
211	/// Collect all strings passed to this [`Builder`].
212	///
213	/// # Panics
214	///
215	/// If the concatenated string would be greater than [`MAX_LENGTH`], this
216	/// panics.
217	#[inline]
218	#[must_use]
219	pub fn collect_unchecked(self) -> TokenString {
220		match self.total_size {
221			| 0 => EMPTY,
222			| 1 ..= MAX_LENGTH_SMALL => self.collect_to_small(),
223			| MAX_LENGTH_SMALL_ADD1 ..= MAX_LENGTH => self.collect_to_alloc(),
224			| _ => panic!(
225				"the result of this builder would be bigger than `MAX_LENGTH`!"
226			),
227		}
228	}
229
230	/// Collect the [`TokenString`]s of the builder into a "small string".
231	fn collect_to_small(&self) -> TokenString {
232		let mut ret_val = EMPTY;
233		#[expect(
234			clippy::cast_possible_truncation,
235			reason = "We checked for overflow in the parent function"
236		)]
237		let total = self.total_size as u16;
238		ret_val.len = total;
239		// SAFETY:
240		// The two arrays, `prefix` and `u.small`, are guaranteed to be
241		// consecutive in memory.
242		let dest: &mut [u8] = unsafe {
243			slice::from_raw_parts_mut(
244				ret_val.prefix.as_mut_ptr(),
245				self.total_size,
246			)
247		};
248		let mut curr_end = 0;
249		for b in self {
250			let len = b.len as usize;
251			let idx = curr_end;
252			curr_end += len;
253			dest[idx .. curr_end].copy_from_slice(&b.as_bytes()[.. len]);
254		}
255
256		ret_val
257	}
258
259	/// Collect the [`TokenString`]s of the builder into the allocated array.
260	fn collect_to_alloc(&self) -> TokenString {
261		let mut ret_val = EMPTY;
262		#[expect(
263			clippy::cast_possible_truncation,
264			reason = "We checked for overflow in the parent function"
265		)]
266		let total = self.total_size as u16;
267		ret_val.u.ptr =
268			mem::ManuallyDrop::new(StringPtr::alloc_manually(self.total_size));
269		ret_val.len = total;
270		let mut curr_end = 0;
271		for b in self {
272			let len = b.len as usize;
273			let idx = curr_end;
274			curr_end += len;
275			// SAFETY:
276			// We've checked, that there is a pointer saved in the union.
277			unsafe {
278				(*ret_val.u.ptr).copy_manually(idx, b.as_bytes());
279			}
280		}
281		// SAFETY:
282		// We've checked, that there is a pointer saved in the union.
283		ret_val.prefix.copy_from_slice(unsafe {
284			&(*ret_val.u.ptr).as_slice_manually_mut(self.total_size)
285				[.. PREFIX_LENGTH]
286		});
287		ret_val
288	}
289}
290
291/// A helper trait for the [`Builder`] type to concatenate the given string at
292/// the end of the other ones in the builder.
293pub trait Concat<T> {
294	type Output;
295
296	/// Concatenate another string to the end of the builder.
297	///
298	/// # Errors
299	///
300	/// This function will return [`TkStrError::TooBig`] if the concatenated
301	/// string is greater than [`MAX_LENGTH`].
302	/// If more than `N` strings, the const generic value of the [`Builder`],
303	/// are being concatenated, [`TkStrError::TooMany`] is returned.
304	fn concat(self, s: T) -> Result<Self::Output, TkStrError>;
305}
306
307impl<'a, const N: usize> Concat<&'a TokenString>
308	for Result<&'a mut Builder<'a, N>, TkStrError>
309{
310	type Output = &'a mut Builder<'a, N>;
311
312	#[inline]
313	fn concat(self, s: &'a TokenString) -> Result<Self::Output, TkStrError> {
314		self?.concat_checked(s)
315	}
316}
317
318impl<'a, const N: usize> Concat<&'a TokenString> for &'a mut Builder<'a, N> {
319	type Output = &'a mut Builder<'a, N>;
320
321	#[inline]
322	fn concat(self, s: &'a TokenString) -> Result<Self::Output, TkStrError> {
323		self.concat_checked(s)
324	}
325}
326
327/// A helper trait for the [`Builder`] type to collect the strings to
328/// concatenate into a single string.
329pub trait Collect {
330	/// Actually concatenate all strings given to the [`Builder`].
331	///
332	/// # Errors
333	///
334	/// Returns [`TkStrError::TooBig`] if the sum of all string lengths is
335	/// greater than [`MAX_LENGTH`].
336	fn collect(self) -> Result<TokenString, TkStrError>;
337}
338
339impl<'a, const N: usize> Collect
340	for Result<&'a mut Builder<'a, N>, TkStrError>
341{
342	#[inline]
343	fn collect(self) -> Result<TokenString, TkStrError> {
344		self?.collect_checked()
345	}
346}
347
348impl<'a, const N: usize> Collect for &'a mut Builder<'a, N> {
349	#[inline]
350	fn collect(self) -> Result<TokenString, TkStrError> {
351		self.collect_checked()
352	}
353}
354
355#[macro_export]
356/// Return the number of arguments passed.
357macro_rules! num_args {
358    () => { 0_usize };
359	($_x:tt $($xs:tt)*) => { 1_usize + $crate::num_args!($($xs)*) };
360}
361
362/// Concatenate all given strings and return a new [`TokenString`].
363///
364/// Warning: if passing a big number of strings to concatenate, this macro may
365/// fail, as it uses recursion to compute the number of arguments.
366///
367/// Memory:
368///
369/// With `N` being the number of strings to concatenate, the [`Builder`] used
370/// internally uses a static array of `N`  pointers to [`TokenString`]s and two
371/// 8 byte fields, so (N + 2) * 8 bytes, (N + 2) * 64 bits.
372///
373/// # Errors
374///
375/// This function will return [`TkStrError::TooBig`] if the concatenated
376/// string is greater than [`MAX_LENGTH`].
377#[macro_export]
378macro_rules! concat {
379	( $x0:expr, $($xs:expr),+ ) => {{
380		$crate::Builder::<'_, {$crate::num_args!($x0 $($xs)*)}>::new($x0)
381		$( .concat($xs) )+.collect()
382	}};
383}
384
385// =============================================================================
386//                                  Tests
387// =============================================================================
388
389
390#[cfg(test)]
391mod prefix {
392	extern crate std;
393	use assert2::check;
394
395	use crate::{Builder, TokenString};
396
397	#[test]
398	fn concat_empty_unchecked() {
399		let s1 = TokenString::default();
400		let res = Builder::<6>::new(&s1).collect_unchecked();
401		check!(res.prefix[0] == 0);
402		check!(res.len == 0);
403	}
404
405	#[test]
406	fn concat_2_empty_unchecked() {
407		let s1 = TokenString::default();
408		let s2 = TokenString::default();
409		let res = Builder::<6>::new(&s1)
410			.concat_unchecked(&s2)
411			.collect_unchecked();
412		check!(res.prefix[0] == 0);
413		check!(res.len == 0);
414	}
415
416	#[test]
417	fn concat_1_unchecked() {
418		let s1 = TokenString::from_str_unchecked("12");
419		let res = Builder::<6>::new(&s1).collect_unchecked();
420		check!(&res.prefix[.. 2] == b"12");
421		check!(res.len == 2);
422	}
423
424	#[test]
425	fn concat_1_empty_unchecked() {
426		let s1 = TokenString::from_str_unchecked("12");
427		let s2 = TokenString::default();
428		let res = Builder::<6>::new(&s1)
429			.concat_unchecked(&s2)
430			.collect_unchecked();
431		check!(&res.prefix[.. 2] == b"12");
432		check!(res.len == 2);
433	}
434
435	#[test]
436	fn concat_empty_1_unchecked() {
437		let s1 = TokenString::default();
438		let s2 = TokenString::from_str_unchecked("12");
439		let res = Builder::<6>::new(&s1)
440			.concat_unchecked(&s2)
441			.collect_unchecked();
442		check!(&res.prefix[.. 2] == b"12");
443	}
444
445
446	#[test]
447	fn concat_2_unchecked() {
448		let s1 = TokenString::from_str_unchecked("12");
449		let s2 = TokenString::from_str_unchecked("34");
450		let res = Builder::<6>::new(&s1)
451			.concat_unchecked(&s2)
452			.collect_unchecked();
453		check!(&res.prefix[.. 4] == b"1234");
454		check!(res.len == 4);
455	}
456
457	#[test]
458	fn concat_1_empty_1_unchecked() {
459		let s1 = TokenString::from_str_unchecked("12");
460		let s2 = TokenString::from_str_unchecked("");
461		let s3 = TokenString::from_str_unchecked("34");
462		let res = Builder::<6>::new(&s1)
463			.concat_unchecked(&s2)
464			.concat_unchecked(&s3)
465			.collect_unchecked();
466		check!(&res.prefix[.. 4] == b"1234");
467		check!(res.len == 4);
468	}
469
470	#[test]
471	fn concat_3_unchecked() {
472		let s1 = TokenString::from_str_unchecked("12");
473		let s2 = TokenString::from_str_unchecked("34");
474		let s3 = TokenString::from_str_unchecked("56");
475		let res = Builder::<6>::new(&s1)
476			.concat_unchecked(&s2)
477			.concat_unchecked(&s3)
478			.collect_unchecked();
479		check!(&res.prefix == b"123456");
480		check!(res.len == 6);
481	}
482	#[test]
483	fn concat_empty_checked() {
484		let s1 = TokenString::default();
485		let res = Builder::<6>::new(&s1).collect_checked().unwrap();
486		check!(res.prefix[0] == 0);
487		check!(res.len == 0);
488	}
489
490	#[test]
491	fn concat_2_empty_checked() {
492		let s1 = TokenString::default();
493		let s2 = TokenString::default();
494		let res = Builder::<6>::new(&s1)
495			.concat_checked(&s2)
496			.unwrap()
497			.collect_checked()
498			.unwrap();
499		check!(res.prefix[0] == 0);
500		check!(res.len == 0);
501	}
502
503	#[test]
504	fn concat_1_checked() {
505		let s1 = TokenString::from_str_unchecked("12");
506		let res = Builder::<6>::new(&s1).collect_checked().unwrap();
507		check!(&res.prefix[.. 2] == b"12");
508		check!(res.len == 2);
509	}
510
511	#[test]
512	fn concat_1_empty_checked() {
513		let s1 = TokenString::from_str_unchecked("12");
514		let s2 = TokenString::default();
515		let res = Builder::<6>::new(&s1)
516			.concat_checked(&s2)
517			.unwrap()
518			.collect_checked()
519			.unwrap();
520		check!(&res.prefix[.. 2] == b"12");
521		check!(res.len == 2);
522	}
523
524	#[test]
525	fn concat_empty_1_checked() {
526		let s1 = TokenString::default();
527		let s2 = TokenString::from_str_unchecked("12");
528		let res = Builder::<6>::new(&s1)
529			.concat_checked(&s2)
530			.unwrap()
531			.collect_checked()
532			.unwrap();
533		check!(&res.prefix[.. 2] == b"12");
534	}
535
536
537	#[test]
538	fn concat_2_checked() {
539		let s1 = TokenString::from_str_unchecked("12");
540		let s2 = TokenString::from_str_unchecked("34");
541		let res = Builder::<6>::new(&s1)
542			.concat_checked(&s2)
543			.unwrap()
544			.collect_checked()
545			.unwrap();
546		check!(&res.prefix[.. 4] == b"1234");
547		check!(res.len == 4);
548	}
549
550	#[test]
551	fn concat_1_empty_1_checked() {
552		let s1 = TokenString::from_str_unchecked("12");
553		let s2 = TokenString::from_str_unchecked("");
554		let s3 = TokenString::from_str_unchecked("34");
555		let res = Builder::<6>::new(&s1)
556			.concat_checked(&s2)
557			.unwrap()
558			.concat_checked(&s3)
559			.unwrap()
560			.collect_checked()
561			.unwrap();
562		check!(&res.prefix[.. 4] == b"1234");
563		check!(res.len == 4);
564	}
565
566	#[test]
567	fn concat_3_checked() {
568		let s1 = TokenString::from_str_unchecked("12");
569		let s2 = TokenString::from_str_unchecked("34");
570		let s3 = TokenString::from_str_unchecked("56");
571		let res = Builder::<6>::new(&s1)
572			.concat_checked(&s2)
573			.unwrap()
574			.concat_checked(&s3)
575			.unwrap()
576			.collect_checked()
577			.unwrap();
578		check!(&res.prefix == b"123456");
579		check!(res.len == 6);
580	}
581}
582
583#[cfg(test)]
584mod small {
585	extern crate std;
586	use assert2::check;
587
588	use crate::{Builder, TokenString};
589
590
591	#[test]
592	fn concat_1_unchecked() {
593		let s1 = TokenString::from_str_unchecked("1234567");
594		let res = Builder::<6>::new(&s1).collect_unchecked();
595		check!(&res.prefix == b"123456");
596		// SAFETY:
597		// The string should be in the `small` field of the union.
598		check!(unsafe { res.u.small[0] } == b'7');
599		check!(res.len == 7);
600	}
601
602	#[test]
603	fn concat_1_empty_unchecked() {
604		let s1 = TokenString::from_str_unchecked("1234567");
605		let s2 = TokenString::default();
606		let res = Builder::<6>::new(&s1)
607			.concat_unchecked(&s2)
608			.collect_unchecked();
609		check!(&res.prefix == b"123456");
610		// SAFETY:
611		// The string should be in the `small` field of the union.
612		check!(unsafe { res.u.small[0] } == b'7');
613		check!(res.len == 7);
614	}
615
616	#[test]
617	fn concat_empty_1_unchecked() {
618		let s1 = TokenString::default();
619		let s2 = TokenString::from_str_unchecked("1234567");
620		let res = Builder::<6>::new(&s1)
621			.concat_unchecked(&s2)
622			.collect_unchecked();
623		check!(&res.prefix == b"123456");
624		// SAFETY:
625		// The string should be in the `small` field of the union.
626		check!(unsafe { res.u.small[0] } == b'7');
627		check!(res.len == 7);
628	}
629
630
631	#[test]
632	fn concat_2_unchecked() {
633		let s1 = TokenString::from_str_unchecked("1234");
634		let s2 = TokenString::from_str_unchecked("5678");
635		let res = Builder::<6>::new(&s1)
636			.concat_unchecked(&s2)
637			.collect_unchecked();
638		check!(&res.prefix[.. 6] == b"123456");
639		// SAFETY:
640		// The string should be in the `small` field of the union.
641		check!(unsafe { &res.u.small[.. 2] } == b"78");
642		check!(res.len == 8);
643	}
644
645	#[test]
646	fn concat_2_pref_unchecked() {
647		let s1 = TokenString::from_str_unchecked("1");
648		let s2 = TokenString::from_str_unchecked("2345678");
649		let res = Builder::<6>::new(&s1)
650			.concat_unchecked(&s2)
651			.collect_unchecked();
652		check!(&res.prefix[.. 6] == b"123456");
653		// SAFETY:
654		// The string should be in the `small` field of the union.
655		check!(unsafe { &res.u.small[.. 2] } == b"78");
656		check!(res.len == 8);
657	}
658
659	#[test]
660	fn concat_1_empty_1_unchecked() {
661		let s1 = TokenString::from_str_unchecked("1234");
662		let s2 = TokenString::from_str_unchecked("");
663		let s3 = TokenString::from_str_unchecked("5678");
664		let res = Builder::<6>::new(&s1)
665			.concat_unchecked(&s2)
666			.concat_unchecked(&s3)
667			.collect_unchecked();
668		check!(&res.prefix[.. 6] == b"123456");
669		// SAFETY:
670		// The string should be in the `small` field of the union.
671		check!(unsafe { &res.u.small[.. 2] } == b"78");
672		check!(res.len == 8);
673	}
674
675	#[test]
676	fn concat_3_unchecked() {
677		let s1 = TokenString::from_str_unchecked("1234");
678		let s2 = TokenString::from_str_unchecked("5678");
679		let s3 = TokenString::from_str_unchecked("90ABCD");
680		let res = Builder::<6>::new(&s1)
681			.concat_unchecked(&s2)
682			.concat_unchecked(&s3)
683			.collect_unchecked();
684		check!(&res.prefix[.. 6] == b"123456");
685		// SAFETY:
686		// The string should be in the `small` field of the union.
687		check!(unsafe { &res.u.small[.. 8] } == b"7890ABCD");
688		check!(res.len == 14);
689	}
690
691	#[test]
692	fn concat_1_checked() {
693		let s1 = TokenString::from_str_unchecked("1234567");
694		let res = Builder::<6>::new(&s1).collect_checked().unwrap();
695		check!(&res.prefix == b"123456");
696		// SAFETY:
697		// The string should be in the `small` field of the union.
698		check!(unsafe { res.u.small[0] } == b'7');
699		check!(res.len == 7);
700	}
701
702	#[test]
703	fn concat_1_empty_checked() {
704		let s1 = TokenString::from_str_unchecked("1234567");
705		let s2 = TokenString::default();
706		let res = Builder::<6>::new(&s1)
707			.concat_checked(&s2)
708			.unwrap()
709			.collect_checked()
710			.unwrap();
711		check!(&res.prefix == b"123456");
712		// SAFETY:
713		// The string should be in the `small` field of the union.
714		check!(unsafe { res.u.small[0] } == b'7');
715		check!(res.len == 7);
716	}
717
718	#[test]
719	fn concat_empty_1_checked() {
720		let s1 = TokenString::default();
721		let s2 = TokenString::from_str_unchecked("1234567");
722		let res = Builder::<6>::new(&s1)
723			.concat_checked(&s2)
724			.unwrap()
725			.collect_checked()
726			.unwrap();
727		check!(&res.prefix == b"123456");
728		// SAFETY:
729		// The string should be in the `small` field of the union.
730		check!(unsafe { res.u.small[0] } == b'7');
731		check!(res.len == 7);
732	}
733
734
735	#[test]
736	fn concat_2_checked() {
737		let s1 = TokenString::from_str_unchecked("1234");
738		let s2 = TokenString::from_str_unchecked("5678");
739		let res = Builder::<6>::new(&s1)
740			.concat_checked(&s2)
741			.unwrap()
742			.collect_checked()
743			.unwrap();
744		check!(&res.prefix[.. 6] == b"123456");
745		// SAFETY:
746		// The string should be in the `small` field of the union.
747		check!(unsafe { &res.u.small[.. 2] } == b"78");
748		check!(res.len == 8);
749	}
750
751	#[test]
752	fn concat_2_pref_checked() {
753		let s1 = TokenString::from_str_unchecked("1");
754		let s2 = TokenString::from_str_unchecked("2345678");
755		let res = Builder::<6>::new(&s1)
756			.concat_checked(&s2)
757			.unwrap()
758			.collect_checked()
759			.unwrap();
760		check!(&res.prefix[.. 6] == b"123456");
761		// SAFETY:
762		// The string should be in the `small` field of the union.
763		check!(unsafe { &res.u.small[.. 2] } == b"78");
764		check!(res.len == 8);
765	}
766
767	#[test]
768	fn concat_1_empty_1_checked() {
769		let s1 = TokenString::from_str_unchecked("1234");
770		let s2 = TokenString::from_str_unchecked("");
771		let s3 = TokenString::from_str_unchecked("5678");
772		let res = Builder::<6>::new(&s1)
773			.concat_checked(&s2)
774			.unwrap()
775			.concat_checked(&s3)
776			.unwrap()
777			.collect_checked()
778			.unwrap();
779		check!(&res.prefix[.. 6] == b"123456");
780		// SAFETY:
781		// The string should be in the `small` field of the union.
782		check!(unsafe { &res.u.small[.. 2] } == b"78");
783		check!(res.len == 8);
784	}
785
786	#[test]
787	fn concat_3_checked() {
788		let s1 = TokenString::from_str_unchecked("1234");
789		let s2 = TokenString::from_str_unchecked("5678");
790		let s3 = TokenString::from_str_unchecked("90ABCD");
791		let res = Builder::<6>::new(&s1)
792			.concat_checked(&s2)
793			.unwrap()
794			.concat_checked(&s3)
795			.unwrap()
796			.collect_checked()
797			.unwrap();
798		check!(&res.prefix[.. 6] == b"123456");
799		// SAFETY:
800		// The string should be in the `small` field of the union.
801		check!(unsafe { &res.u.small[.. 8] } == b"7890ABCD");
802		check!(res.len == 14);
803	}
804}
805
806#[cfg(test)]
807mod heap {
808	extern crate std;
809	use assert2::check;
810
811	use crate::{Builder, TokenString};
812
813
814	#[test]
815	fn concat_1_unchecked() {
816		let s1 = TokenString::from_str_unchecked("1234567890ABCDE");
817		let res = Builder::<6>::new(&s1).collect_unchecked();
818		check!(&res.prefix == b"123456");
819		check!(
820			// SAFETY:
821			// The string should be in the `ptr` field of the union.
822			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
823				== "1234567890ABCDE"
824		);
825		check!(res.len == 15);
826	}
827
828	#[test]
829	fn concat_1_empty_unchecked() {
830		let s1 = TokenString::from_str_unchecked("1234567890ABCDE");
831		let s2 = TokenString::default();
832		let res = Builder::<6>::new(&s1)
833			.concat_unchecked(&s2)
834			.collect_unchecked();
835		check!(&res.prefix == b"123456");
836		check!(
837			// SAFETY:
838			// The string should be in the `ptr` field of the union.
839			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
840				== "1234567890ABCDE"
841		);
842		check!(res.len == 15);
843	}
844
845	#[test]
846	fn concat_empty_1_unchecked() {
847		let s1 = TokenString::default();
848		let s2 = TokenString::from_str_unchecked("1234567890ABCDE");
849		let res = Builder::<6>::new(&s1)
850			.concat_unchecked(&s2)
851			.collect_unchecked();
852		check!(&res.prefix == b"123456");
853		check!(
854			// SAFETY:
855			// The string should be in the `ptr` field of the union.
856			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
857				== "1234567890ABCDE"
858		);
859		check!(res.len == 15);
860	}
861
862
863	#[test]
864	fn concat_2_unchecked() {
865		let s1 = TokenString::from_str_unchecked("1234");
866		let s2 = TokenString::from_str_unchecked("567890ABCDE");
867		let res = Builder::<6>::new(&s1)
868			.concat_unchecked(&s2)
869			.collect_unchecked();
870		check!(&res.prefix[.. 6] == b"123456");
871		check!(
872			// SAFETY:
873			// The string should be in the `ptr` field of the union.
874			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
875				== "1234567890ABCDE"
876		);
877		check!(res.len == 15);
878	}
879
880	#[test]
881	fn concat_1_empty_1_unchecked() {
882		let s1 = TokenString::from_str_unchecked("1234");
883		let s2 = TokenString::from_str_unchecked("");
884		let s3 = TokenString::from_str_unchecked("567890ABCDE");
885		let res = Builder::<6>::new(&s1)
886			.concat_unchecked(&s2)
887			.concat_unchecked(&s3)
888			.collect_unchecked();
889		check!(&res.prefix[.. 6] == b"123456");
890		check!(
891			// SAFETY:
892			// The string should be in the `ptr` field of the union.
893			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
894				== "1234567890ABCDE"
895		);
896		check!(res.len == 15);
897	}
898
899	#[test]
900	fn concat_3_unchecked() {
901		let s1 = TokenString::from_str_unchecked("1234");
902		let s2 = TokenString::from_str_unchecked("5678");
903		let s3 = TokenString::from_str_unchecked("90ABCDE");
904		let res = Builder::<6>::new(&s1)
905			.concat_unchecked(&s2)
906			.concat_unchecked(&s3)
907			.collect_unchecked();
908		check!(&res.prefix[.. 6] == b"123456");
909		check!(
910			// SAFETY:
911			// The string should be in the `ptr` field of the union.
912			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
913				== "1234567890ABCDE"
914		);
915		check!(res.len == 15);
916	}
917
918	#[test]
919	fn concat_1_checked() {
920		let s1 = TokenString::from_str_unchecked("1234567890ABCDE");
921		let res = Builder::<6>::new(&s1).collect_checked().unwrap();
922		check!(&res.prefix == b"123456");
923		check!(
924			// SAFETY:
925			// The string should be in the `ptr` field of the union.
926			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
927				== "1234567890ABCDE"
928		);
929		check!(res.len == 15);
930	}
931
932	#[test]
933	fn concat_1_empty_checked() {
934		let s1 = TokenString::from_str_unchecked("1234567890ABCDE");
935		let s2 = TokenString::default();
936		let res = Builder::<6>::new(&s1)
937			.concat_checked(&s2)
938			.unwrap()
939			.collect_checked()
940			.unwrap();
941		check!(&res.prefix == b"123456");
942		check!(
943			// SAFETY:
944			// The string should be in the `ptr` field of the union.
945			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
946				== "1234567890ABCDE"
947		);
948		check!(res.len == 15);
949	}
950
951	#[test]
952	fn concat_empty_1_checked() {
953		let s1 = TokenString::default();
954		let s2 = TokenString::from_str_unchecked("1234567890ABCDE");
955		let res = Builder::<6>::new(&s1)
956			.concat_checked(&s2)
957			.unwrap()
958			.collect_checked()
959			.unwrap();
960		check!(&res.prefix == b"123456");
961		check!(
962			// SAFETY:
963			// The string should be in the `ptr` field of the union.
964			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
965				== "1234567890ABCDE"
966		);
967		check!(res.len == 15);
968	}
969
970
971	#[test]
972	fn concat_2_checked() {
973		let s1 = TokenString::from_str_unchecked("1234");
974		let s2 = TokenString::from_str_unchecked("567890ABCDE");
975		let res = Builder::<6>::new(&s1)
976			.concat_checked(&s2)
977			.unwrap()
978			.collect_checked()
979			.unwrap();
980		check!(&res.prefix[.. 6] == b"123456");
981		check!(
982			// SAFETY:
983			// The string should be in the `ptr` field of the union.
984			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
985				== "1234567890ABCDE"
986		);
987		check!(res.len == 15);
988	}
989
990	#[test]
991	fn concat_1_empty_1_checked() {
992		let s1 = TokenString::from_str_unchecked("1234");
993		let s2 = TokenString::from_str_unchecked("");
994		let s3 = TokenString::from_str_unchecked("567890ABCDE");
995		let res = Builder::<6>::new(&s1)
996			.concat_checked(&s2)
997			.unwrap()
998			.concat_checked(&s3)
999			.unwrap()
1000			.collect_checked()
1001			.unwrap();
1002		check!(&res.prefix[.. 6] == b"123456");
1003		check!(
1004			// SAFETY:
1005			// The string should be in the `ptr` field of the union.
1006			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
1007				== "1234567890ABCDE"
1008		);
1009		check!(res.len == 15);
1010	}
1011
1012	#[test]
1013	fn concat_3_checked() {
1014		let s1 = TokenString::from_str_unchecked("1234");
1015		let s2 = TokenString::from_str_unchecked("5678");
1016		let s3 = TokenString::from_str_unchecked("90ABCDE");
1017		let res = Builder::<6>::new(&s1)
1018			.concat_checked(&s2)
1019			.unwrap()
1020			.concat_checked(&s3)
1021			.unwrap()
1022			.collect_checked()
1023			.unwrap();
1024		check!(&res.prefix[.. 6] == b"123456");
1025		check!(
1026			// SAFETY:
1027			// The string should be in the `ptr` field of the union.
1028			unsafe { res.u.ptr.as_string_manually(res.len as usize) }
1029				== "1234567890ABCDE"
1030		);
1031		check!(res.len == 15);
1032	}
1033}