btt 0.5.0

Binary to text encodings
Documentation
use core::{
	fmt::{self, Debug},
	mem,
	ops::Deref,
	slice
};

#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Alphabet<const LEN: usize>([u8; LEN]);

impl<const LEN: usize> Alphabet<LEN> {
	#[track_caller]
	pub const fn new(alphabet: &[u8; LEN]) -> Result<&Self, AlphabetError> {
		let mut i = 0;
		while i < LEN {
			if alphabet[i] >= 128 {
				return Err(AlphabetError::NonAscii(alphabet[i]));
			}

			let mut j = 0;
			while j < i {
				if alphabet[i] == alphabet[j] {
					return Err(AlphabetError::Duplicate {
						character: alphabet[i],
						first: j,
						second: i
					});
				}

				j += 1;
			}

			i += 1;
		}

		const {
			assert!(LEN < 128);
		}

		#[allow(clippy::missing_transmute_annotations)]
		Ok(unsafe { mem::transmute(alphabet) })
	}

	#[allow(clippy::missing_safety_doc)]
	#[track_caller]
	pub const unsafe fn new_unchecked(alphabet: &[u8; LEN]) -> &Self {
		debug_assert!(Self::new(alphabet).is_ok());
		unsafe { mem::transmute(alphabet) }
	}

	pub const fn as_dyn_alphabet(&self) -> &DynAlphabet {
		unsafe { mem::transmute(slice::from_raw_parts(self.0.as_ptr(), LEN)) }
	}

	pub const fn as_bytes(&self) -> &[u8; LEN] {
		&self.0
	}

	pub const fn as_str(&self) -> &str {
		unsafe { str::from_utf8_unchecked(self.as_bytes()) }
	}

	pub const fn decoding_table(&self) -> DecodingTable<LEN> {
		DecodingTable::from_alphabet(self)
	}
}

impl<const LEN: usize> Debug for Alphabet<LEN> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "Alphabet<{LEN}>({})", self.as_str().escape_debug())
	}
}

impl<const LEN: usize> AsRef<Alphabet<LEN>> for Alphabet<LEN> {
	fn as_ref(&self) -> &Alphabet<LEN> {
		self
	}
}

impl<const LEN: usize> AsRef<DynAlphabet> for Alphabet<LEN> {
	fn as_ref(&self) -> &DynAlphabet {
		self.as_dyn_alphabet()
	}
}

impl<const LEN: usize> AsRef<[u8; LEN]> for Alphabet<LEN> {
	fn as_ref(&self) -> &[u8; LEN] {
		&self.0
	}
}

impl<const LEN: usize> AsRef<[u8]> for Alphabet<LEN> {
	fn as_ref(&self) -> &[u8] {
		self.as_bytes()
	}
}

impl<const LEN: usize> Deref for Alphabet<LEN> {
	type Target = [u8; LEN];

	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

#[derive(Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct DynAlphabet([u8]);

impl DynAlphabet {
	#[inline]
	pub const fn as_bytes(&self) -> &[u8] {
		&self.0
	}

	#[inline]
	pub const fn as_str(&self) -> &str {
		unsafe { str::from_utf8_unchecked(self.as_bytes()) }
	}
}

impl Debug for DynAlphabet {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "Alphabet<{}>({})", self.0.len(), self.as_str().escape_debug())
	}
}

impl AsRef<[u8]> for DynAlphabet {
	#[inline]
	fn as_ref(&self) -> &[u8] {
		&self.0
	}
}

impl Deref for DynAlphabet {
	type Target = [u8];

	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct DecodingTable<const LEN: usize>([u8; 256]);

impl<const LEN: usize> DecodingTable<LEN> {
	#[inline]
	pub const fn from_alphabet(alphabet: &Alphabet<LEN>) -> Self {
		const {
			assert!(LEN < 128);
		}

		let mut table = Self([0xff; 256]);
		let mut i = 0;

		while i < LEN {
			let _ = table.set(alphabet.0[i], i as u8);
			i += 1;
		}

		table
	}

	#[inline]
	pub(crate) const fn from_slices(slices: &[&[u8]; LEN]) -> Option<Self> {
		const {
			assert!(LEN < 128);
		}

		let mut table = Self([0xff; 256]);
		let mut i = 0;

		while i < LEN {
			let characters = slices[i];

			if characters.is_empty() {
				return None;
			}

			let mut j = 0;
			while j < characters.len() {
				let _ = table.set(characters[j], i as u8);
				j += 1;
			}

			i += 1;
		}

		Some(table)
	}

	#[inline]
	pub const fn merge(&self, other: &Self) -> Option<Self> {
		let mut table = Self([0xff; 256]);
		let mut i = 0;

		while i < 256 {
			let v = match (self.0[i], other.0[i]) {
				(a, b) if a == b => a,
				(a, 0xff) => a,
				(0xff, b) => b,
				_ => return None
			};

			if v != 0xff {
				let _ = table.set(i as u8, v);
			}

			i += 1;
		}

		Some(table)
	}

	#[inline]
	pub const fn as_bytes(&self) -> &[u8; 256] {
		&self.0
	}

	pub const fn as_dyn_decoding_table(&self) -> &DynDecodingTable {
		unsafe { mem::transmute(&self.0) }
	}

	#[inline]
	pub fn decode(&self, character: u8) -> Option<u8> {
		let b = self[character as usize];

		if b != 0xff {
			debug_assert!((b as usize) < LEN);
			Some(b)
		} else {
			None
		}
	}

	#[inline]
	pub(crate) const fn check(&self, alphabet: &Alphabet<LEN>) -> bool {
		let mut i = 0;

		while i < LEN {
			if self.0[alphabet.0[i] as usize] != i as u8 {
				return false;
			}

			i += 1;
		}

		true
	}

	#[track_caller]
	pub(crate) const fn set(&mut self, character: u8, value: u8) -> &mut Self {
		assert!((value as usize) < LEN);
		assert!(self.0[character as usize] == 0xff);
		self.0[character as usize] = value;
		self
	}
}

impl<const LEN: usize> AsRef<DecodingTable<LEN>> for DecodingTable<LEN> {
	fn as_ref(&self) -> &DecodingTable<LEN> {
		self
	}
}

impl<const LEN: usize> AsRef<DynDecodingTable> for DecodingTable<LEN> {
	fn as_ref(&self) -> &DynDecodingTable {
		self.as_dyn_decoding_table()
	}
}

impl<const LEN: usize> AsRef<[u8; 256]> for DecodingTable<LEN> {
	fn as_ref(&self) -> &[u8; 256] {
		&self.0
	}
}

impl<const LEN: usize> Deref for DecodingTable<LEN> {
	type Target = [u8; 256];

	#[inline]
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct DynDecodingTable([u8; 256]);

impl AsRef<[u8; 256]> for DynDecodingTable {
	#[inline]
	fn as_ref(&self) -> &[u8; 256] {
		&self.0
	}
}

impl Deref for DynDecodingTable {
	type Target = [u8; 256];

	#[inline]
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum AlphabetError {
	NonAscii(u8),
	Duplicate { character: u8, first: usize, second: usize }
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn alphabet() {
		assert_eq!(Alphabet::new(b"").unwrap().as_str(), "");
		assert_eq!(Alphabet::new(b"123").unwrap().as_str(), "123");
		assert_eq!(Alphabet::new(b"\0\n\t\x7f").unwrap().as_str(), "\0\n\t\x7f");
		assert_eq!(Alphabet::new(b"\x80"), Err(AlphabetError::NonAscii(0x80)));

		assert_eq!(
			Alphabet::new(b"aa"),
			Err(AlphabetError::Duplicate { character: b'a', first: 0, second: 1 })
		);
	}
}