Short (up to 65,535 bytes) immutable strings to e.g. parse tokens, implemented in Rust. These are sometimes called "German Strings", because Germans have written the paper mentioning them
at main 1041 lines 28 kB view raw
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}