usecrate::asciibyte::AsciiByte; usecrate::int_ops::{Aligned4, Aligned8}; usecrate::TinyStrError; use core::fmt; use core::ops::Deref; use core::str::{self, FromStr};
impl<const N: usize> TinyAsciiStr<N> { /// Creates a `TinyAsciiStr<N>` from the given byte slice. /// `bytes` may contain at most `N` non-null ASCII bytes. pubconstfn from_bytes(bytes: &[u8]) -> Result<Self, TinyStrError> { Self::from_bytes_inner(bytes, 0, bytes.len(), false)
}
/// Creates a `TinyAsciiStr<N>` from a byte slice, replacing invalid bytes. /// /// Null and non-ASCII bytes (i.e. those outside the range `0x01..=0x7F`) /// will be replaced with the '?' character. /// /// The input slice will be truncated if its length exceeds `N`. pubconstfn from_bytes_lossy(bytes: &[u8]) -> Self { const QUESTION: u8 = b'?'; letmut out = [0; N]; letmut i = 0; // Ord is not available in const, so no `.min(N)` let len = if bytes.len() > N { N } else { bytes.len() };
// Indexing is protected by the len check above #[allow(clippy::indexing_slicing)] while i < len { let b = bytes[i]; if b > 0 && b < 0x80 {
out[i] = b;
} else {
out[i] = QUESTION;
}
i += 1;
}
Self { // SAFETY: `out` only contains ASCII bytes and has same size as `self.bytes`
bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
}
}
/// Attempts to parse a fixed-length byte array to a `TinyAsciiStr`. /// /// The byte array may contain trailing NUL bytes. /// /// # Example /// /// ``` /// use tinystr::tinystr; /// use tinystr::TinyAsciiStr; /// /// assert_eq!( /// TinyAsciiStr::<3>::try_from_raw(*b"GB\0"), /// Ok(tinystr!(3, "GB")) /// ); /// assert_eq!( /// TinyAsciiStr::<3>::try_from_raw(*b"USD"), /// Ok(tinystr!(3, "USD")) /// ); /// assert!(matches!(TinyAsciiStr::<3>::try_from_raw(*b"\0A\0"), Err(_))); /// ``` pubconstfn try_from_raw(raw: [u8; N]) -> Result<Self, TinyStrError> { Self::from_bytes_inner(&raw, 0, N, true)
}
/// Equivalent to [`from_bytes(bytes[start..end])`](Self::from_bytes), /// but callable in a `const` context (which range indexing is not). pubconstfn from_bytes_manual_slice(
bytes: &[u8],
start: usize,
end: usize,
) -> Result<Self, TinyStrError> { Self::from_bytes_inner(bytes, start, end, false)
}
#[inline] pub(crate) constfn from_bytes_inner(
bytes: &[u8],
start: usize,
end: usize,
allow_trailing_null: bool,
) -> Result<Self, TinyStrError> { let len = end - start; if len > N { return Err(TinyStrError::TooLarge { max: N, len });
}
letmut out = [0; N]; letmut i = 0; letmut found_null = false; // Indexing is protected by TinyStrError::TooLarge #[allow(clippy::indexing_slicing)] while i < len { let b = bytes[start + i];
if b == 0 {
found_null = true;
} elseif b >= 0x80 { return Err(TinyStrError::NonAscii);
} elseif found_null { // Error if there are contentful bytes after null return Err(TinyStrError::ContainsNull);
}
out[i] = b;
i += 1;
}
if !allow_trailing_null && found_null { // We found some trailing nulls, error return Err(TinyStrError::ContainsNull);
}
Ok(Self { // SAFETY: `out` only contains ASCII bytes and has same size as `self.bytes`
bytes: unsafe { AsciiByte::to_ascii_byte_array(&out) },
})
}
// TODO: This function shadows the FromStr trait. Rename? #[inline] pubconstfn from_str(s: &str) -> Result<Self, TinyStrError> { Self::from_bytes_inner(s.as_bytes(), 0, s.len(), false)
}
#[inline] #[must_use] pubconstfn len(&self) -> usize { if N <= 4 {
Aligned4::from_ascii_bytes(&self.bytes).len()
} elseif N <= 8 {
Aligned8::from_ascii_bytes(&self.bytes).len()
} else { letmut i = 0; #[allow(clippy::indexing_slicing)] // < N is safe while i < N && self.bytes[i] as u8 != AsciiByte::B0 as u8 {
i += 1
}
i
}
}
#[inline] #[must_use] pubconstfn is_empty(&self) -> bool { self.bytes[0] as u8 == AsciiByte::B0 as u8
}
#[inline] #[must_use] pubconstfn as_bytes(&self) -> &[u8] { // Safe because `self.bytes.as_slice()` pointer-casts to `&[u8]`, // and changing the length of that slice to self.len() < N is safe. unsafe {
core::slice::from_raw_parts(self.bytes.as_slice().as_ptr() as *const u8, self.len())
}
}
#[inline] #[must_use] pubconstfn all_bytes(&self) -> &[u8; N] { // SAFETY: `self.bytes` has same size as [u8; N] unsafe { &*(self.bytes.as_ptr() as *const [u8; N]) }
}
#[inline] #[must_use] /// Resizes a `TinyAsciiStr<N>` to a `TinyAsciiStr<M>`. /// /// If `M < len()` the string gets truncated, otherwise only the /// memory representation changes. pubconstfn resize<const M: usize>(self) -> TinyAsciiStr<M> { letmut bytes = [0; M]; letmut i = 0; // Indexing is protected by the loop guard #[allow(clippy::indexing_slicing)] while i < M && i < N {
bytes[i] = self.bytes[i] as u8;
i += 1;
} // `self.bytes` only contains ASCII bytes, with no null bytes between // ASCII characters, so this also holds for `bytes`. unsafe { TinyAsciiStr::from_bytes_unchecked(bytes) }
}
/// # Safety /// Must be called with a bytes array made of valid ASCII bytes, with no null bytes /// between ASCII characters #[must_use] pubconstunsafefn from_bytes_unchecked(bytes: [u8; N]) -> Self { Self {
bytes: AsciiByte::to_ascii_byte_array(&bytes),
}
}
}
macro_rules! check_is {
($self:ident, $check_int:ident, $check_u8:ident) => { if N <= 4 {
Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
} elseif N <= 8 {
Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
} else { letmut i = 0; // Won't panic because self.bytes has length N #[allow(clippy::indexing_slicing)] while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 { if !($self.bytes[i] as u8).$check_u8() { returnfalse;
}
i += 1;
} true
}
};
($self:ident, $check_int:ident, !$check_u8_0_inv:ident, !$check_u8_1_inv:ident) => { if N <= 4 {
Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
} elseif N <= 8 {
Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
} else { // Won't panic because N is > 8 if ($self.bytes[0] as u8).$check_u8_0_inv() { returnfalse;
} letmut i = 1; // Won't panic because self.bytes has length N #[allow(clippy::indexing_slicing)] while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 { if ($self.bytes[i] as u8).$check_u8_1_inv() { returnfalse;
}
i += 1;
} true
}
};
($self:ident, $check_int:ident, $check_u8_0_inv:ident, $check_u8_1_inv:ident) => { if N <= 4 {
Aligned4::from_ascii_bytes(&$self.bytes).$check_int()
} elseif N <= 8 {
Aligned8::from_ascii_bytes(&$self.bytes).$check_int()
} else { // Won't panic because N is > 8 if !($self.bytes[0] as u8).$check_u8_0_inv() { returnfalse;
} letmut i = 1; // Won't panic because self.bytes has length N #[allow(clippy::indexing_slicing)] while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 { if !($self.bytes[i] as u8).$check_u8_1_inv() { returnfalse;
}
i += 1;
} true
}
};
}
impl<const N: usize> TinyAsciiStr<N> { /// Checks if the value is composed of ASCII alphabetic characters: /// /// * U+0041 'A' ..= U+005A 'Z', or /// * U+0061 'a' ..= U+007A 'z'. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "Te3t".parse().expect("Failed to parse."); /// /// assert!(s1.is_ascii_alphabetic()); /// assert!(!s2.is_ascii_alphabetic()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_alphabetic(&self) -> bool {
check_is!(self, is_ascii_alphabetic, is_ascii_alphabetic)
}
/// Checks if the value is composed of ASCII alphanumeric characters: /// /// * U+0041 'A' ..= U+005A 'Z', or /// * U+0061 'a' ..= U+007A 'z', or /// * U+0030 '0' ..= U+0039 '9'. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "A15b".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "[3@w".parse().expect("Failed to parse."); /// /// assert!(s1.is_ascii_alphanumeric()); /// assert!(!s2.is_ascii_alphanumeric()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_alphanumeric(&self) -> bool {
check_is!(self, is_ascii_alphanumeric, is_ascii_alphanumeric)
}
/// Checks if the value is composed of ASCII decimal digits: /// /// * U+0030 '0' ..= U+0039 '9'. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "312".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "3d".parse().expect("Failed to parse."); /// /// assert!(s1.is_ascii_numeric()); /// assert!(!s2.is_ascii_numeric()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_numeric(&self) -> bool {
check_is!(self, is_ascii_numeric, is_ascii_digit)
}
/// Checks if the value is in ASCII lower case. /// /// All letter characters are checked for case. Non-letter characters are ignored. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "test".parse().expect("Failed to parse."); /// let s3: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse."); /// /// assert!(!s1.is_ascii_lowercase()); /// assert!(s2.is_ascii_lowercase()); /// assert!(s3.is_ascii_lowercase()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_lowercase(&self) -> bool {
check_is!( self,
is_ascii_lowercase,
!is_ascii_uppercase,
!is_ascii_uppercase
)
}
/// Checks if the value is in ASCII title case. /// /// This verifies that the first character is ASCII uppercase and all others ASCII lowercase. /// Non-letter characters are ignored. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse."); /// let s3: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse."); /// /// assert!(!s1.is_ascii_titlecase()); /// assert!(s2.is_ascii_titlecase()); /// assert!(s3.is_ascii_titlecase()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_titlecase(&self) -> bool {
check_is!( self,
is_ascii_titlecase,
!is_ascii_lowercase,
!is_ascii_uppercase
)
}
/// Checks if the value is in ASCII upper case. /// /// All letter characters are checked for case. Non-letter characters are ignored. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "TEST".parse().expect("Failed to parse."); /// let s3: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse."); /// /// assert!(!s1.is_ascii_uppercase()); /// assert!(s2.is_ascii_uppercase()); /// assert!(!s3.is_ascii_uppercase()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_uppercase(&self) -> bool {
check_is!( self,
is_ascii_uppercase,
!is_ascii_lowercase,
!is_ascii_lowercase
)
}
/// Checks if the value is composed of ASCII alphabetic lower case characters: /// /// * U+0061 'a' ..= U+007A 'z', /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "Te3t".parse().expect("Failed to parse."); /// let s3: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse."); /// let s4: TinyAsciiStr<4> = "test".parse().expect("Failed to parse."); /// let s5: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse."); /// /// assert!(!s1.is_ascii_alphabetic_lowercase()); /// assert!(!s2.is_ascii_alphabetic_lowercase()); /// assert!(!s3.is_ascii_alphabetic_lowercase()); /// assert!(s4.is_ascii_alphabetic_lowercase()); /// assert!(!s5.is_ascii_alphabetic_lowercase()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_alphabetic_lowercase(&self) -> bool {
check_is!( self,
is_ascii_alphabetic_lowercase,
is_ascii_lowercase,
is_ascii_lowercase
)
}
/// Checks if the value is composed of ASCII alphabetic, with the first character being ASCII uppercase, and all others ASCII lowercase. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "Te3t".parse().expect("Failed to parse."); /// let s3: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse."); /// let s4: TinyAsciiStr<4> = "test".parse().expect("Failed to parse."); /// let s5: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse."); /// /// assert!(s1.is_ascii_alphabetic_titlecase()); /// assert!(!s2.is_ascii_alphabetic_titlecase()); /// assert!(!s3.is_ascii_alphabetic_titlecase()); /// assert!(!s4.is_ascii_alphabetic_titlecase()); /// assert!(!s5.is_ascii_alphabetic_titlecase()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_alphabetic_titlecase(&self) -> bool {
check_is!( self,
is_ascii_alphabetic_titlecase,
is_ascii_uppercase,
is_ascii_lowercase
)
}
/// Checks if the value is composed of ASCII alphabetic upper case characters: /// /// * U+0041 'A' ..= U+005A 'Z', /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "Test".parse().expect("Failed to parse."); /// let s2: TinyAsciiStr<4> = "Te3t".parse().expect("Failed to parse."); /// let s3: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse."); /// let s4: TinyAsciiStr<4> = "TEST".parse().expect("Failed to parse."); /// let s5: TinyAsciiStr<4> = "001z".parse().expect("Failed to parse."); /// /// assert!(!s1.is_ascii_alphabetic_uppercase()); /// assert!(!s2.is_ascii_alphabetic_uppercase()); /// assert!(!s3.is_ascii_alphabetic_uppercase()); /// assert!(s4.is_ascii_alphabetic_uppercase()); /// assert!(!s5.is_ascii_alphabetic_uppercase()); /// ``` #[inline] #[must_use] pubconstfn is_ascii_alphabetic_uppercase(&self) -> bool {
check_is!( self,
is_ascii_alphabetic_uppercase,
is_ascii_uppercase,
is_ascii_uppercase
)
}
}
macro_rules! to {
($self:ident, $to:ident, $later_char_to:ident $(,$first_char_to:ident)?) => {{ letmut i = 0; if N <= 4 { let aligned = Aligned4::from_ascii_bytes(&$self.bytes).$to().to_ascii_bytes(); // Won't panic because self.bytes has length N and aligned has length >= N #[allow(clippy::indexing_slicing)] while i < N {
$self.bytes[i] = aligned[i];
i += 1;
}
} elseif N <= 8 { let aligned = Aligned8::from_ascii_bytes(&$self.bytes).$to().to_ascii_bytes(); // Won't panic because self.bytes has length N and aligned has length >= N #[allow(clippy::indexing_slicing)] while i < N {
$self.bytes[i] = aligned[i];
i += 1;
}
} else { // Won't panic because self.bytes has length N #[allow(clippy::indexing_slicing)] while i < N && $self.bytes[i] as u8 != AsciiByte::B0 as u8 { // SAFETY: AsciiByte is repr(u8) and has same size as u8 unsafe {
$self.bytes[i] = core::mem::transmute::<u8, AsciiByte>(
($self.bytes[i] as u8).$later_char_to()
);
}
i += 1;
} // SAFETY: AsciiByte is repr(u8) and has same size as u8
$(
$self.bytes[0] = unsafe {
core::mem::transmute::<u8, AsciiByte>(($self.bytes[0] as u8).$first_char_to())
};
)?
}
$self
}};
}
impl<const N: usize> TinyAsciiStr<N> { /// Converts this type to its ASCII lower case equivalent in-place. /// /// ASCII letters 'A' to 'Z' are mapped to 'a' to 'z', other characters are unchanged. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "TeS3".parse().expect("Failed to parse."); /// /// assert_eq!(&*s1.to_ascii_lowercase(), "tes3"); /// ``` #[inline] #[must_use] pubconstfn to_ascii_lowercase(mutself) -> Self {
to!(self, to_ascii_lowercase, to_ascii_lowercase)
}
/// Converts this type to its ASCII title case equivalent in-place. /// /// The first character is converted to ASCII uppercase; the remaining characters /// are converted to ASCII lowercase. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "teSt".parse().expect("Failed to parse."); /// /// assert_eq!(&*s1.to_ascii_titlecase(), "Test"); /// ``` #[inline] #[must_use] pubconstfn to_ascii_titlecase(mutself) -> Self {
to!( self,
to_ascii_titlecase,
to_ascii_lowercase,
to_ascii_uppercase
)
}
/// Converts this type to its ASCII upper case equivalent in-place. /// /// ASCII letters 'a' to 'z' are mapped to 'A' to 'Z', other characters are unchanged. /// /// # Examples /// /// ``` /// use tinystr::TinyAsciiStr; /// /// let s1: TinyAsciiStr<4> = "Tes3".parse().expect("Failed to parse."); /// /// assert_eq!(&*s1.to_ascii_uppercase(), "TES3"); /// ``` #[inline] #[must_use] pubconstfn to_ascii_uppercase(mutself) -> Self {
to!(self, to_ascii_uppercase, to_ascii_uppercase)
}
}
#[cfg(test)] mod test { usesuper::*; use rand::distributions::Distribution; use rand::distributions::Standard; use rand::rngs::SmallRng; use rand::seq::SliceRandom; use rand::SeedableRng;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.