construe 0.0.3

Compile-Time Growable Array: Vec & String for const!
Documentation
use crate::*;

// no_std core::fmt::Write implementor
// credit Shepmaster: https://stackoverflow.com/a/39491059
// we use this to compare Display::write against core::fmt::Display
struct Wrapper<'a> {
	buf: &'a mut [u8],
	offset: usize,
}
impl<'a> Wrapper<'a> {
	fn new(buf: &'a mut [u8]) -> Self {
		Wrapper { buf, offset: 0 }
	}
	fn borrow_str(&self) -> &str {
		match core::str::from_utf8(&self.buf[..self.offset]) {
			Ok(slice_as_str) => slice_as_str,
			Err(_e) => panic!("assembled byte slice contains invalid UTF-8")
		}
	}
}
impl<'a> core::fmt::Write for Wrapper<'a> {
	fn write_str(&mut self, s: &str) -> core::fmt::Result {
		let bytes = s.as_bytes();

		// Skip over already-copied data
		let remainder = &mut self.buf[self.offset..];
		// Check if there is space remaining (return error instead of panicking)
		if remainder.len() < bytes.len() { return Err(core::fmt::Error); }
		// Make the two slices the same length
		let remainder = &mut remainder[..bytes.len()];
		// Copy
		remainder.copy_from_slice(bytes);

		// Update offset to avoid overwriting
		self.offset += bytes.len();

		Ok(())
	}
}

#[test]
fn write_macro() {
	use crate::write;
	let mut sc = StrConstrue::<11>::new();
	write!(sc, "Test", 123u8, " ");
	write!(sc, ":^)");

	let arr = sc.store_bytes();
	let txt = StrConstrue::borrow_str(&arr);
	assert_eq!(txt, "Test123 :^)");
}

#[test]
fn display_char() {
	const MAX_SIZE: usize = 4;

	let chars = "Construe".chars();
	let numbers = "01234567890".chars();
	let special = [char::MAX, char::REPLACEMENT_CHARACTER];
	let weird = "🤔 🫠 🫥 🇨 🇧🇧 🫵 🦘 🇹🇰".chars();

	for ch in chars.chain(numbers).chain(special).chain(weird) {
		let mut sc = StrConstrue::<MAX_SIZE>::new();
		sc = Display(ch).write(sc);
		let ch_size = sc.len();
		while sc.len() < MAX_SIZE {
			sc = sc.push_str(" ");
		}
		let arr = sc.store_bytes();
		let text = StrConstrue::borrow_str(&arr[0..ch_size]);

		let mut buf = [0u8; MAX_SIZE];
		let mut writer = Wrapper::new(&mut buf);
		core::fmt::write(&mut writer, format_args!("{ch}")).unwrap();
		let text_2 = writer.borrow_str();

		assert_eq!(text, text_2);
	}
}

#[test]
fn display_str() {
	let mut sc = StrConstrue::<10>::new();
	sc = Display("1234567890").write(sc);
	let x = sc.store_bytes();
	assert_eq!(StrConstrue::borrow_str(&x), "1234567890");
}

#[test]
fn itoa() {
	// i128::MIN has 40 decimal characters
	const MAX_SIZE: usize = 40;

	macro_rules! check_ints {
		($i:ty => $c:expr) => {
			let cases = $c.iter().chain(&[<$i>::MIN, <$i>::MAX]);
			let check_case = |case: $i| {
				let mut sc = StrConstrue::<MAX_SIZE>::new();
				sc = Display::<$i>(case).write(sc);
				let int_size = sc.len();
				while sc.len() < MAX_SIZE {
					sc = sc.push_str(" ");
				}
				let arr = sc.store_bytes();
				let text = StrConstrue::borrow_str(&arr[0..int_size]);

				let mut buf = [0u8; MAX_SIZE];
				let mut writer = Wrapper::new(&mut buf);
				core::fmt::write(
					&mut writer,
					format_args!("{case}")
				).unwrap();
				let text_2 = writer.borrow_str();

				assert_eq!(text, text_2);
			};
			for &case in cases {
				check_case(case);
				case.checked_neg().map(check_case);
			}
		};
		($($i:ty => $c:expr),*) => {
			$( check_ints!($i => $c) );*
		};
	}
	check_ints!(
		u8 => [1, 12, 123],
		u16 => [1, 12, 123, 1234, 12345],
		u32 => [1, 12, 123, 1234, 12345, 123456, 1234567, 1234567890],
		u64 => [1, 12, 123, 1234, 12345, 123456, 1234567, 1234567890],
		u128 => [1, 12, 123, 1234, 12345, 123456, 1234567, 1234567890],
		usize => [1, 12, 123, 1234, 12345, 123456, 1234567, 1234567890]
	);
	check_ints!(
		i8 => [0, 1, 12, 123],
		i16 => [0, 1, 12, 123, 1234, 12345],
		i32 => [0, 1, 12, 123, 1234, 12345, 123456, 1234567, 1234567890],
		i64 => [0, 1, 12, 123, 1234, 12345, 123456, 1234567, 1234567890],
		i128 => [0, 1, 12, 123, 1234, 12345, 123456, 1234567, 1234567890],
		isize => [0, 1, 12, 123, 1234, 12345, 123456, 1234567, 1234567890]
	);
}

#[test]
fn try_construe() {
	const fn multiply<const N: usize, T: Copy>(mut slice: &[T], times: usize)
		-> Construe<T, N>
	{
		let mut c = Construe::new();
		while let [item, rest @ ..] = slice {
			slice = rest;
			let mut i = 0;
			while i < times {
				c = c.push(*item).0;
				i += 1;
			}
		}
		c
	}
	const fn concat<const N: usize>(mut slice: &[&str], times: usize)
		-> StrConstrue<N>
	{
		let mut sc = StrConstrue::new();
		while let [s, rest @ ..] = slice {
			slice = rest;
			let mut i = 0;
			while i < times {
				sc = sc.push_str(s);
				i += 1;
			}
		}
		sc
	}

	const WORDS: &[&str] = &{
		const LEN: usize = multiply(&["hey", "you"], 2).needs_len();
		const ARRAY: [&str; LEN] = multiply(&["hey", "you"], 2).finish();
		ARRAY
	};

	assert_eq!(WORDS, &["hey", "hey", "you", "you"]);

	const WORDS2: &[&str] = construe!(&[&str] => multiply(&["hey", "you"], 2));
	assert_eq!(WORDS, WORDS2);

	construe!(const WORDS3: &[&str] = multiply(&["hey", "you"], 2));
	assert_eq!(WORDS, WORDS3);

	construe!(const WORDS4: [&str; _] = multiply(&["hey", "you"], 2));
	assert_eq!(WORDS, &WORDS4);

	construe!(const ONE_WORD: &str = concat(&["hey", "you"], 2));
	assert_eq!(ONE_WORD, "heyheyyouyou");

	// since traits require `&'static` for some reason...
	trait Trait {
		const STR: &'static str;
		const ARR: &'static [u8];
	}
	impl Trait for () {
		construe!(const STR: &'static str = concat(&["a", "b"], 10));
		construe!(const ARR: &'static [u8] = multiply(&[1, 2, 3], 9));
	}
}