// SPDX-FileCopyrightText: Copyright (C) 2024 Roland Csaszar // SPDX-License-Identifier: MPL-2.0 // // Project: token-string // File: builder.rs // Date: 22.Nov.2024 // ============================================================================= //! String builder, concatenation of [`TokenString`]s. use crate::{ EMPTY, MAX_LENGTH, MAX_LENGTH_SMALL, MAX_LENGTH_SMALL_ADD1, PREFIX_LENGTH, StringPtr, TkStrError, TokenString, }; extern crate alloc; use core::{fmt, marker, mem, ptr, slice}; /// A struct to concatenate [`TokenString`]s without allocating unnecessary /// memory on the heap. /// /// The const generic parameter `N` should be set to the number of strings to /// concatenate. See the [`crate::concat!`] macro for a more comfortable way to /// use a [`Builder`]. /// /// Memory: /// /// Uses a static array of `N` pointers to [`TokenString`]s and two 8 byte /// fields, so (N + 2) * 8 bytes, (N + 2) * 64 bits. #[derive(Debug, Clone, Copy)] pub struct Builder<'a, const N: usize> { /// The array of strings to concatenate. strings: [*const TokenString; N], /// The size of the concatenated string. total_size: usize, /// The number of strings to concatenate. num_strings: usize, /// Phantom field to hold the lifetime of the [`TokenString`] pointers. lifetime: marker::PhantomData<&'a ()>, } /// An iterator over the [`TokenString`]s of a [`Builder`]. /// /// A way to iterator over the strings to concatenate in a [`Builder`]. #[derive(Debug)] pub struct BuilderIter<'a, const N: usize> { /// The [`Builder`] to iterate over. builder: &'a Builder<'a, N>, /// The current index in the array of [`TokenString`]s to concatenate. idx: usize, } impl fmt::Display for Builder<'_, N> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Builder < ")?; for idx in 0 .. self.num_strings { if idx == 0 { write!( f, "'{}'", // SAFETY: // The pointers have been constructed from valid // references, and are not null. unsafe { &*self.strings[idx] } )?; } else { write!( f, " + '{}'", // SAFETY: // The pointers have been constructed from valid // references, and are not null. unsafe { &*self.strings[idx] } )?; } } write!(f, " >") } } impl<'a, const N: usize> IntoIterator for &'a Builder<'a, N> { type IntoIter = BuilderIter<'a, N>; type Item = &'a TokenString; #[inline] fn into_iter(self) -> Self::IntoIter { Self::IntoIter { builder: self, idx: 0, } } } impl<'a, const N: usize> Iterator for BuilderIter<'a, N> { type Item = &'a TokenString; #[inline] fn next(&mut self) -> Option { debug_assert!( self.idx <= self.builder.num_strings, "Builder iterator index is out of bounds" ); self.idx += 1; if self.idx - 1 == self.builder.num_strings { None } else { // SAFETY: // The pointers have been constructed from valid references, and // are not null. Some(unsafe { &*self.builder.strings[self.idx - 1] }) } } } impl<'a, const N: usize> Builder<'a, N> { #[inline] #[must_use] pub fn iter(&'a self) -> BuilderIter<'a, N> { <&Self as IntoIterator>::into_iter(self) } /// Create a new [`Builder`] from the given [`TokenString`]. /// /// Use this to concatenate the given string with other strings. /// /// # Panics /// /// Panics, if the number of strings to concatenate is set to 0. #[inline] #[must_use] pub const fn new(s: &'a TokenString) -> Self { assert!(N > 0, "the number of elements must not be 0"); let mut ret_val = Self { total_size: s.len as usize, strings: [ptr::null(); N], num_strings: 1, lifetime: marker::PhantomData, }; ret_val.strings[0] = s; ret_val } /// Concatenate the given string at the end of the builder. /// /// See also [`Builder::concat_checked`] for the version that checks for /// errors, or the trait method [`Concat::concat`]. /// /// # Panics /// /// Panics, if the number of concatenated strings is bigger than the size /// reserved by the constant generic `N` of [`Builder`]. #[inline] #[must_use] pub const fn concat_unchecked(&mut self, s: &'a TokenString) -> &mut Self { assert!( (self.num_strings < N), "more strings concatenated than reserved space in Builder" ); self.total_size += s.len as usize; self.strings[self.num_strings] = s; self.num_strings += 1; self } /// Concatenate another string to the end of the builder. /// /// See also [`Builder::concat_unchecked`] for the version that does not /// check for errors, or the trait method [`concat`]. /// /// # Errors /// /// This function will return [`TkStrError::TooBig`] if the concatenated /// string is greater than [`MAX_LENGTH`]. /// If more than `N`, the const generic value of the [`Builder`], strings /// are being concatenated, [`TkStrError::TooMany`] is returned. #[inline] pub const fn concat_checked( &mut self, s: &'a TokenString, ) -> Result<&mut Self, TkStrError> { if self.num_strings == N { return Err(TkStrError::TooMany(N)); } if self.total_size + s.len as usize > MAX_LENGTH { return Err(TkStrError::TooBig(self.total_size + s.len as usize)); } self.total_size += s.len as usize; self.strings[self.num_strings] = s; self.num_strings += 1; Ok(self) } /// Collect all strings passed to this [`Builder`]. /// /// # Errors /// /// Returns [`TkStrError::TooBig`] if the sum of all string lengths is /// greater than [`MAX_LENGTH`]. #[inline] pub fn collect_checked(self) -> Result { match self.total_size { | 0 => Ok(EMPTY), | 1 ..= MAX_LENGTH_SMALL => Ok(self.collect_to_small()), | MAX_LENGTH_SMALL_ADD1 ..= MAX_LENGTH => Ok(self.collect_to_alloc()), | _ => Err(TkStrError::TooBig(self.total_size)), } } /// Collect all strings passed to this [`Builder`]. /// /// # Panics /// /// If the concatenated string would be greater than [`MAX_LENGTH`], this /// panics. #[inline] #[must_use] pub fn collect_unchecked(self) -> TokenString { match self.total_size { | 0 => EMPTY, | 1 ..= MAX_LENGTH_SMALL => self.collect_to_small(), | MAX_LENGTH_SMALL_ADD1 ..= MAX_LENGTH => self.collect_to_alloc(), | _ => panic!( "the result of this builder would be bigger than `MAX_LENGTH`!" ), } } /// Collect the [`TokenString`]s of the builder into a "small string". fn collect_to_small(&self) -> TokenString { let mut ret_val = EMPTY; #[expect( clippy::cast_possible_truncation, reason = "We checked for overflow in the parent function" )] let total = self.total_size as u16; ret_val.len = total; // SAFETY: // The two arrays, `prefix` and `u.small`, are guaranteed to be // consecutive in memory. let dest: &mut [u8] = unsafe { slice::from_raw_parts_mut( ret_val.prefix.as_mut_ptr(), self.total_size, ) }; let mut curr_end = 0; for b in self { let len = b.len as usize; let idx = curr_end; curr_end += len; dest[idx .. curr_end].copy_from_slice(&b.as_bytes()[.. len]); } ret_val } /// Collect the [`TokenString`]s of the builder into the allocated array. fn collect_to_alloc(&self) -> TokenString { let mut ret_val = EMPTY; #[expect( clippy::cast_possible_truncation, reason = "We checked for overflow in the parent function" )] let total = self.total_size as u16; ret_val.u.ptr = mem::ManuallyDrop::new(StringPtr::alloc_manually(self.total_size)); ret_val.len = total; let mut curr_end = 0; for b in self { let len = b.len as usize; let idx = curr_end; curr_end += len; // SAFETY: // We've checked, that there is a pointer saved in the union. unsafe { (*ret_val.u.ptr).copy_manually(idx, b.as_bytes()); } } // SAFETY: // We've checked, that there is a pointer saved in the union. ret_val.prefix.copy_from_slice(unsafe { &(*ret_val.u.ptr).as_slice_manually_mut(self.total_size) [.. PREFIX_LENGTH] }); ret_val } } /// A helper trait for the [`Builder`] type to concatenate the given string at /// the end of the other ones in the builder. pub trait Concat { type Output; /// Concatenate another string to the end of the builder. /// /// # Errors /// /// This function will return [`TkStrError::TooBig`] if the concatenated /// string is greater than [`MAX_LENGTH`]. /// If more than `N` strings, the const generic value of the [`Builder`], /// are being concatenated, [`TkStrError::TooMany`] is returned. fn concat(self, s: T) -> Result; } impl<'a, const N: usize> Concat<&'a TokenString> for Result<&'a mut Builder<'a, N>, TkStrError> { type Output = &'a mut Builder<'a, N>; #[inline] fn concat(self, s: &'a TokenString) -> Result { self?.concat_checked(s) } } impl<'a, const N: usize> Concat<&'a TokenString> for &'a mut Builder<'a, N> { type Output = &'a mut Builder<'a, N>; #[inline] fn concat(self, s: &'a TokenString) -> Result { self.concat_checked(s) } } /// A helper trait for the [`Builder`] type to collect the strings to /// concatenate into a single string. pub trait Collect { /// Actually concatenate all strings given to the [`Builder`]. /// /// # Errors /// /// Returns [`TkStrError::TooBig`] if the sum of all string lengths is /// greater than [`MAX_LENGTH`]. fn collect(self) -> Result; } impl<'a, const N: usize> Collect for Result<&'a mut Builder<'a, N>, TkStrError> { #[inline] fn collect(self) -> Result { self?.collect_checked() } } impl<'a, const N: usize> Collect for &'a mut Builder<'a, N> { #[inline] fn collect(self) -> Result { self.collect_checked() } } #[macro_export] /// Return the number of arguments passed. macro_rules! num_args { () => { 0_usize }; ($_x:tt $($xs:tt)*) => { 1_usize + $crate::num_args!($($xs)*) }; } /// Concatenate all given strings and return a new [`TokenString`]. /// /// Warning: if passing a big number of strings to concatenate, this macro may /// fail, as it uses recursion to compute the number of arguments. /// /// Memory: /// /// With `N` being the number of strings to concatenate, the [`Builder`] used /// internally uses a static array of `N` pointers to [`TokenString`]s and two /// 8 byte fields, so (N + 2) * 8 bytes, (N + 2) * 64 bits. /// /// # Errors /// /// This function will return [`TkStrError::TooBig`] if the concatenated /// string is greater than [`MAX_LENGTH`]. #[macro_export] macro_rules! concat { ( $x0:expr, $($xs:expr),+ ) => {{ $crate::Builder::<'_, {$crate::num_args!($x0 $($xs)*)}>::new($x0) $( .concat($xs) )+.collect() }}; } // ============================================================================= // Tests // ============================================================================= #[cfg(test)] mod prefix { extern crate std; use assert2::check; use crate::{Builder, TokenString}; #[test] fn concat_empty_unchecked() { let s1 = TokenString::default(); let res = Builder::<6>::new(&s1).collect_unchecked(); check!(res.prefix[0] == 0); check!(res.len == 0); } #[test] fn concat_2_empty_unchecked() { let s1 = TokenString::default(); let s2 = TokenString::default(); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(res.prefix[0] == 0); check!(res.len == 0); } #[test] fn concat_1_unchecked() { let s1 = TokenString::from_str_unchecked("12"); let res = Builder::<6>::new(&s1).collect_unchecked(); check!(&res.prefix[.. 2] == b"12"); check!(res.len == 2); } #[test] fn concat_1_empty_unchecked() { let s1 = TokenString::from_str_unchecked("12"); let s2 = TokenString::default(); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix[.. 2] == b"12"); check!(res.len == 2); } #[test] fn concat_empty_1_unchecked() { let s1 = TokenString::default(); let s2 = TokenString::from_str_unchecked("12"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix[.. 2] == b"12"); } #[test] fn concat_2_unchecked() { let s1 = TokenString::from_str_unchecked("12"); let s2 = TokenString::from_str_unchecked("34"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix[.. 4] == b"1234"); check!(res.len == 4); } #[test] fn concat_1_empty_1_unchecked() { let s1 = TokenString::from_str_unchecked("12"); let s2 = TokenString::from_str_unchecked(""); let s3 = TokenString::from_str_unchecked("34"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .concat_unchecked(&s3) .collect_unchecked(); check!(&res.prefix[.. 4] == b"1234"); check!(res.len == 4); } #[test] fn concat_3_unchecked() { let s1 = TokenString::from_str_unchecked("12"); let s2 = TokenString::from_str_unchecked("34"); let s3 = TokenString::from_str_unchecked("56"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .concat_unchecked(&s3) .collect_unchecked(); check!(&res.prefix == b"123456"); check!(res.len == 6); } #[test] fn concat_empty_checked() { let s1 = TokenString::default(); let res = Builder::<6>::new(&s1).collect_checked().unwrap(); check!(res.prefix[0] == 0); check!(res.len == 0); } #[test] fn concat_2_empty_checked() { let s1 = TokenString::default(); let s2 = TokenString::default(); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(res.prefix[0] == 0); check!(res.len == 0); } #[test] fn concat_1_checked() { let s1 = TokenString::from_str_unchecked("12"); let res = Builder::<6>::new(&s1).collect_checked().unwrap(); check!(&res.prefix[.. 2] == b"12"); check!(res.len == 2); } #[test] fn concat_1_empty_checked() { let s1 = TokenString::from_str_unchecked("12"); let s2 = TokenString::default(); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 2] == b"12"); check!(res.len == 2); } #[test] fn concat_empty_1_checked() { let s1 = TokenString::default(); let s2 = TokenString::from_str_unchecked("12"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 2] == b"12"); } #[test] fn concat_2_checked() { let s1 = TokenString::from_str_unchecked("12"); let s2 = TokenString::from_str_unchecked("34"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 4] == b"1234"); check!(res.len == 4); } #[test] fn concat_1_empty_1_checked() { let s1 = TokenString::from_str_unchecked("12"); let s2 = TokenString::from_str_unchecked(""); let s3 = TokenString::from_str_unchecked("34"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .concat_checked(&s3) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 4] == b"1234"); check!(res.len == 4); } #[test] fn concat_3_checked() { let s1 = TokenString::from_str_unchecked("12"); let s2 = TokenString::from_str_unchecked("34"); let s3 = TokenString::from_str_unchecked("56"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .concat_checked(&s3) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix == b"123456"); check!(res.len == 6); } } #[cfg(test)] mod small { extern crate std; use assert2::check; use crate::{Builder, TokenString}; #[test] fn concat_1_unchecked() { let s1 = TokenString::from_str_unchecked("1234567"); let res = Builder::<6>::new(&s1).collect_unchecked(); check!(&res.prefix == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { res.u.small[0] } == b'7'); check!(res.len == 7); } #[test] fn concat_1_empty_unchecked() { let s1 = TokenString::from_str_unchecked("1234567"); let s2 = TokenString::default(); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { res.u.small[0] } == b'7'); check!(res.len == 7); } #[test] fn concat_empty_1_unchecked() { let s1 = TokenString::default(); let s2 = TokenString::from_str_unchecked("1234567"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { res.u.small[0] } == b'7'); check!(res.len == 7); } #[test] fn concat_2_unchecked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked("5678"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix[.. 6] == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { &res.u.small[.. 2] } == b"78"); check!(res.len == 8); } #[test] fn concat_2_pref_unchecked() { let s1 = TokenString::from_str_unchecked("1"); let s2 = TokenString::from_str_unchecked("2345678"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix[.. 6] == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { &res.u.small[.. 2] } == b"78"); check!(res.len == 8); } #[test] fn concat_1_empty_1_unchecked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked(""); let s3 = TokenString::from_str_unchecked("5678"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .concat_unchecked(&s3) .collect_unchecked(); check!(&res.prefix[.. 6] == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { &res.u.small[.. 2] } == b"78"); check!(res.len == 8); } #[test] fn concat_3_unchecked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked("5678"); let s3 = TokenString::from_str_unchecked("90ABCD"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .concat_unchecked(&s3) .collect_unchecked(); check!(&res.prefix[.. 6] == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { &res.u.small[.. 8] } == b"7890ABCD"); check!(res.len == 14); } #[test] fn concat_1_checked() { let s1 = TokenString::from_str_unchecked("1234567"); let res = Builder::<6>::new(&s1).collect_checked().unwrap(); check!(&res.prefix == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { res.u.small[0] } == b'7'); check!(res.len == 7); } #[test] fn concat_1_empty_checked() { let s1 = TokenString::from_str_unchecked("1234567"); let s2 = TokenString::default(); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { res.u.small[0] } == b'7'); check!(res.len == 7); } #[test] fn concat_empty_1_checked() { let s1 = TokenString::default(); let s2 = TokenString::from_str_unchecked("1234567"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { res.u.small[0] } == b'7'); check!(res.len == 7); } #[test] fn concat_2_checked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked("5678"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 6] == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { &res.u.small[.. 2] } == b"78"); check!(res.len == 8); } #[test] fn concat_2_pref_checked() { let s1 = TokenString::from_str_unchecked("1"); let s2 = TokenString::from_str_unchecked("2345678"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 6] == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { &res.u.small[.. 2] } == b"78"); check!(res.len == 8); } #[test] fn concat_1_empty_1_checked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked(""); let s3 = TokenString::from_str_unchecked("5678"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .concat_checked(&s3) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 6] == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { &res.u.small[.. 2] } == b"78"); check!(res.len == 8); } #[test] fn concat_3_checked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked("5678"); let s3 = TokenString::from_str_unchecked("90ABCD"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .concat_checked(&s3) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 6] == b"123456"); // SAFETY: // The string should be in the `small` field of the union. check!(unsafe { &res.u.small[.. 8] } == b"7890ABCD"); check!(res.len == 14); } } #[cfg(test)] mod heap { extern crate std; use assert2::check; use crate::{Builder, TokenString}; #[test] fn concat_1_unchecked() { let s1 = TokenString::from_str_unchecked("1234567890ABCDE"); let res = Builder::<6>::new(&s1).collect_unchecked(); check!(&res.prefix == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_1_empty_unchecked() { let s1 = TokenString::from_str_unchecked("1234567890ABCDE"); let s2 = TokenString::default(); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_empty_1_unchecked() { let s1 = TokenString::default(); let s2 = TokenString::from_str_unchecked("1234567890ABCDE"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_2_unchecked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked("567890ABCDE"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .collect_unchecked(); check!(&res.prefix[.. 6] == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_1_empty_1_unchecked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked(""); let s3 = TokenString::from_str_unchecked("567890ABCDE"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .concat_unchecked(&s3) .collect_unchecked(); check!(&res.prefix[.. 6] == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_3_unchecked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked("5678"); let s3 = TokenString::from_str_unchecked("90ABCDE"); let res = Builder::<6>::new(&s1) .concat_unchecked(&s2) .concat_unchecked(&s3) .collect_unchecked(); check!(&res.prefix[.. 6] == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_1_checked() { let s1 = TokenString::from_str_unchecked("1234567890ABCDE"); let res = Builder::<6>::new(&s1).collect_checked().unwrap(); check!(&res.prefix == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_1_empty_checked() { let s1 = TokenString::from_str_unchecked("1234567890ABCDE"); let s2 = TokenString::default(); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_empty_1_checked() { let s1 = TokenString::default(); let s2 = TokenString::from_str_unchecked("1234567890ABCDE"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_2_checked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked("567890ABCDE"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 6] == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_1_empty_1_checked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked(""); let s3 = TokenString::from_str_unchecked("567890ABCDE"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .concat_checked(&s3) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 6] == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } #[test] fn concat_3_checked() { let s1 = TokenString::from_str_unchecked("1234"); let s2 = TokenString::from_str_unchecked("5678"); let s3 = TokenString::from_str_unchecked("90ABCDE"); let res = Builder::<6>::new(&s1) .concat_checked(&s2) .unwrap() .concat_checked(&s3) .unwrap() .collect_checked() .unwrap(); check!(&res.prefix[.. 6] == b"123456"); check!( // SAFETY: // The string should be in the `ptr` field of the union. unsafe { res.u.ptr.as_string_manually(res.len as usize) } == "1234567890ABCDE" ); check!(res.len == 15); } }