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