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};
#[derive(Debug, Clone, Copy)]
pub struct Builder<'a, const N: usize> {
strings: [*const TokenString; N],
total_size: usize,
num_strings: usize,
lifetime: marker::PhantomData<&'a ()>,
}
#[derive(Debug)]
pub struct BuilderIter<'a, const N: usize> {
builder: &'a Builder<'a, N>,
idx: usize,
}
impl<const N: usize> 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, "'{}'", unsafe { &*self.strings[idx] })?;
} else {
write!(f, " + '{}'", 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<Self::Item> {
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 {
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)
}
#[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
}
#[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
}
#[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)
}
#[inline]
pub fn collect_checked(self) -> Result<TokenString, TkStrError> {
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)),
}
}
#[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`!"
),
}
}
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;
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
}
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;
unsafe {
(*ret_val.u.ptr).copy_manually(idx, b.as_bytes());
}
}
ret_val.prefix.copy_from_slice(unsafe {
&(*ret_val.u.ptr).as_slice_manually_mut(self.total_size)
[.. PREFIX_LENGTH]
});
ret_val
}
}
pub trait Concat<T> {
type Output;
fn concat(self, s: T) -> Result<Self::Output, TkStrError>;
}
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::Output, TkStrError> {
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::Output, TkStrError> {
self.concat_checked(s)
}
}
pub trait Collect {
fn collect(self) -> Result<TokenString, TkStrError>;
}
impl<'a, const N: usize> Collect
for Result<&'a mut Builder<'a, N>, TkStrError>
{
#[inline]
fn collect(self) -> Result<TokenString, TkStrError> {
self?.collect_checked()
}
}
impl<'a, const N: usize> Collect for &'a mut Builder<'a, N> {
#[inline]
fn collect(self) -> Result<TokenString, TkStrError> {
self.collect_checked()
}
}
#[macro_export]
macro_rules! num_args {
() => { 0_usize };
($_x:tt $($xs:tt)*) => { 1_usize + $crate::num_args!($($xs)*) };
}
#[macro_export]
macro_rules! concat {
( $x0:expr, $($xs:expr),+ ) => {{
$crate::Builder::<'_, {$crate::num_args!($x0 $($xs)*)}>::new($x0)
$( .concat($xs) )+.collect()
}};
}
#[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");
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");
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");
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");
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");
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");
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");
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");
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");
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");
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");
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");
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");
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");
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!(
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!(
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!(
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!(
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!(
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!(
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!(
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!(
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!(
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!(
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!(
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!(
unsafe { res.u.ptr.as_string_manually(res.len as usize) }
== "1234567890ABCDE"
);
check!(res.len == 15);
}
}