nibblecode 0.1.0

A serialization format based on rkyv
Documentation
//! An archived string representation that supports inlining short strings.

use core::borrow::Borrow;
use core::ops::{Deref, Index};
use core::{fmt, str};
use std::cmp::Ordering;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::mem::ManuallyDrop;
use std::ops::DerefMut;
use std::ptr;
use std::slice::SliceIndex;
use std::str::{from_utf8_unchecked, from_utf8_unchecked_mut};

use crate::list::ArchivedList;
use crate::primitive::FixedUsize;

/// The maximum number of bytes that can be inlined.
pub const INLINE_CAPACITY: usize = size_of::<ArchivedList<u8>>();
/// The maximum number of bytes that can be out-of-line.
pub const OUT_OF_LINE_CAPACITY: usize = (1 << (FixedUsize::BITS - 2)) - 1;

/// An archived [`String`].
///
/// This has inline and out-of-line representations. Short strings will use the
/// available space inside the structure to store the string, and long strings
/// will store a pointer to a `str` instead.
pub union ArchivedString {
	pub(crate) out_of_line: ManuallyDrop<ArchivedList<u8>>,
	pub(crate) inline: [u8; INLINE_CAPACITY],
}

/// See [`ArchivedString::decode`]
pub(crate) enum DecodedString {
	Inline,
	OutOfLine { len: usize, offset: usize },
}

impl ArchivedString {
	/// Determine where this string's contents are.
	#[inline]
	pub(crate) fn decode(&self) -> DecodedString {
		unsafe {
			if self.inline[0] & 0b1100_0000 == 0b1000_0000 {
				let len = self.out_of_line.len.to_native() as usize;
				// Little-endian: remove the 7th and 8th bits
				#[cfg(not(feature = "big_endian"))]
				let len = (len & 0b0011_1111) | ((len & !0xff) >> 2);
				// Big-endian: remove the top two bits
				#[cfg(feature = "big_endian")]
				let len = len & (FixedUsize::MAX >> 2);

				DecodedString::OutOfLine {
					len,
					offset: self.out_of_line.offset.to_native() as usize,
				}
			} else {
				DecodedString::Inline
			}
		}
	}

	/// Extracts a byte slice containing the entire `ArchivedString`.
	#[inline]
	fn as_ptr(&self) -> *const [u8] {
		unsafe {
			match self.decode() {
				DecodedString::Inline => {
					if let Some(string_end) = self.inline.iter().position(|&b| b == 0xff) {
						&raw const self.inline[..string_end]
					} else {
						&raw const self.inline
					}
				}
				DecodedString::OutOfLine { len, offset } => {
					ptr::from_raw_parts(<*const ArchivedString>::cast::<u8>(self).add(offset), len)
				}
			}
		}
	}

	/// Extracts a byte slice containing the entire `ArchivedString`.
	#[inline]
	#[must_use]
	pub fn as_bytes(&self) -> &[u8] {
		unsafe { &*self.as_ptr() }
	}

	/// Extracts a string slice containing the entire `ArchivedString`.
	#[inline]
	#[must_use]
	pub fn as_str(&self) -> &str {
		unsafe { from_utf8_unchecked(self.as_bytes()) }
	}

	/// Extracts a mutable string slice containing the entire `ArchivedString`.
	#[inline]
	#[must_use]
	pub fn as_str_mut(&mut self) -> &mut str {
		unsafe { from_utf8_unchecked_mut(&mut *self.as_ptr().cast_mut()) }
	}
}

impl AsRef<str> for ArchivedString {
	#[inline]
	fn as_ref(&self) -> &str {
		self.as_str()
	}
}

impl Borrow<str> for ArchivedString {
	#[inline]
	fn borrow(&self) -> &str {
		self.as_str()
	}
}

impl Debug for ArchivedString {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		Debug::fmt(self.as_str(), f)
	}
}

impl Deref for ArchivedString {
	type Target = str;

	#[inline]
	fn deref(&self) -> &str {
		self.as_str()
	}
}

impl DerefMut for ArchivedString {
	#[inline]
	fn deref_mut(&mut self) -> &mut str {
		self.as_str_mut()
	}
}

impl Display for ArchivedString {
	#[inline]
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		f.pad(self.as_str())
	}
}

impl Eq for ArchivedString {}

impl Hash for ArchivedString {
	fn hash<H: Hasher>(&self, state: &mut H) {
		self.as_str().hash(state);
	}
}

impl<Idx: SliceIndex<str>> Index<Idx> for ArchivedString {
	type Output = Idx::Output;

	#[inline]
	fn index(&self, index: Idx) -> &Idx::Output {
		self.as_str().index(index)
	}
}

impl Ord for ArchivedString {
	#[inline]
	fn cmp(&self, other: &ArchivedString) -> Ordering {
		self.as_bytes().cmp(other.as_bytes())
	}
}

impl PartialEq for ArchivedString {
	#[inline]
	fn eq(&self, other: &ArchivedString) -> bool {
		self.as_bytes() == other.as_bytes()
	}
}

impl PartialOrd for ArchivedString {
	#[inline]
	fn partial_cmp(&self, other: &ArchivedString) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

impl PartialEq<&str> for ArchivedString {
	#[inline]
	fn eq(&self, other: &&str) -> bool {
		self.as_bytes() == other.as_bytes()
	}
}

impl PartialEq<str> for ArchivedString {
	#[inline]
	fn eq(&self, other: &str) -> bool {
		self.as_bytes() == other.as_bytes()
	}
}

impl PartialEq<ArchivedString> for &str {
	#[inline]
	fn eq(&self, other: &ArchivedString) -> bool {
		self.as_bytes() == other.as_bytes()
	}
}

impl PartialEq<ArchivedString> for str {
	#[inline]
	fn eq(&self, other: &ArchivedString) -> bool {
		self.as_bytes() == other.as_bytes()
	}
}

impl PartialOrd<&str> for ArchivedString {
	#[inline]
	fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
		Some(self.as_bytes().cmp(other.as_bytes()))
	}
}

impl PartialOrd<str> for ArchivedString {
	#[inline]
	fn partial_cmp(&self, other: &str) -> Option<Ordering> {
		Some(self.as_bytes().cmp(other.as_bytes()))
	}
}

impl PartialOrd<ArchivedString> for &str {
	#[inline]
	fn partial_cmp(&self, other: &ArchivedString) -> Option<Ordering> {
		Some(self.as_bytes().cmp(other.as_bytes()))
	}
}

impl PartialOrd<ArchivedString> for str {
	#[inline]
	fn partial_cmp(&self, other: &ArchivedString) -> Option<Ordering> {
		Some(self.as_bytes().cmp(other.as_bytes()))
	}
}