// Copyright 2024 Gabriel Bjørnager Jensen. // // This file is part of bzipper. // // bzipper is free software: you can redistribute // it and/or modify it under the terms of the GNU // Lesser General Public License as published by // the Free Software Foundation, either version 3 // of the License, or (at your option) any later // version. // // bzipper is distributed in the hope that it will // be useful, but WITHOUT ANY WARRANTY; without // even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Less- // er General Public License along with bzipper. If // not, see . #[cfg(test)] mod test; use crate::{Deserialise, Error, FixedIter, Serialise}; use core::cmp::Ordering; use core::fmt::{Debug, Display, Formatter}; use core::mem::MaybeUninit; use core::ops::{Deref, DerefMut, Index, IndexMut}; use core::slice::SliceIndex; use core::str::FromStr; #[cfg(feature = "alloc")] use alloc::string::{String, ToString}; /// Owned string with maximum size. /// /// This is in contrast to [String] -- which has no size limit in practice -- and [str], which is unsized. /// /// # Examples /// /// All instances of this type have the same size if the value of `N` is also the same. /// This size can be found through /// /// `size_of::() * N + size_of::()`. /// /// Therefore, the following four strings have -- despite their different contents -- the same total size. /// /// ``` /// use bzipper::FixedString; /// use std::str::FromStr; /// /// let str0 = FixedString::<0xF>::new(); // Empty string. /// let str1 = FixedString::<0xF>::from_str("Hello there!"); /// let str2 = FixedString::<0xF>::from_str("أنا من أوروپا"); /// let str3 = FixedString::<0xF>::from_str("COGITO ERGO SUM"); /// /// assert_eq!(size_of_val(&str0), size_of_val(&str1)); /// assert_eq!(size_of_val(&str0), size_of_val(&str2)); /// assert_eq!(size_of_val(&str0), size_of_val(&str3)); /// assert_eq!(size_of_val(&str1), size_of_val(&str2)); /// assert_eq!(size_of_val(&str1), size_of_val(&str3)); /// assert_eq!(size_of_val(&str2), size_of_val(&str3)); /// ``` /// /// These three strings can---by extend in theory---also interchange their contents between each other. #[derive(Clone, Deserialise, Serialise)] pub struct FixedString { buf: [char; N], len: usize, } impl FixedString { /// Constructs a new, fixed-size string. /// /// Note that it is only the internal buffer that is size-constrained. /// The string internally keeps track of the amount of used characters and acts accordingly. /// One must therefore only see the value of `N` as a size *limit*. /// /// The constructed string will have a null length. /// All characters inside the internal buffer are instanced as `U+0000 NULL`. /// /// For constructing a string with an already defined buffer, see [`from_chars`](Self::from_chars) and [`from_raw_parts`](Self::from_raw_parts). #[inline(always)] #[must_use] pub const fn new() -> Self { Self { buf: ['\0'; N], len: 0x0 } } /// Consumes the buffer into a fixed string. /// /// The internal length is to `N`. /// For a similar function but with an explicit size, see [`from_raw_parts`](Self::from_raw_parts). #[inline(always)] #[must_use] pub const fn from_chars(buf: [char; N]) -> Self { Self { buf, len: N } } /// Constructs a fixed string from raw parts. #[inline(always)] #[must_use] pub const fn from_raw_parts(buf: [char; N], len: usize) -> Self { Self { buf, len } } /// Deconstructs a fixed string into its raw parts. #[inline(always)] #[must_use] pub const fn into_raw_parts(self) -> ([char; N], usize) { (self.buf, self.len) } /// Gets a pointer to the first character. #[inline(always)] #[must_use] pub const fn as_ptr(&self) -> *const char { self.buf.as_ptr() } /// Gets a mutable pointer to the first character. /// /// This function can only be marked as `const` when `const_mut_refs` is implemented. /// See tracking issue [`#57349`](https://github.com/rust-lang/rust/issues/57349/) for more information. #[inline(always)] #[must_use] pub fn as_mut_ptr(&mut self) -> *mut char { self.buf.as_mut_ptr() } /// Borrows the string as a character slice. /// /// The range of the returned slice only includes characters that are "used." /// For borrowing the entire internal buffer, see [`as_mut_slice`](Self::as_mut_slice). #[inline(always)] #[must_use] pub const fn as_slice(&self) -> &[char] { // We need to use `from_raw_parts` to mark this // function `const`. unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) } } /// Mutably borrows the string as a character slice. /// /// The range of the returned slice includes the entire internal buffer. /// For borrowing only the "used" characters, see [`as_slice`](Self::as_slice). #[inline(always)] #[must_use] pub fn as_mut_slice(&mut self) -> &mut [char] { &mut self.buf[0x0..self.len] } /// Returns the length of the string. /// /// This does not necessarily equate to the value of `N`, as the internal buffer may be used but partially. #[inline(always)] #[must_use] pub const fn len(&self) -> usize { self.len } /// Checks if the string is empty, i.e. `self.len() == 0x0`. #[inline(always)] #[must_use] pub const fn is_empty(&self) -> bool { self.len() == 0x0 } /// Checks if the string is full, i.e. `self.len() == N`. #[inline(always)] #[must_use] pub const fn is_full(&self) -> bool { self.len() == N } /// Sets the internal length. /// /// The length is compared with `N` to guarantee that bounds are honoured. /// /// # Panics /// /// This method panics if the value of `len` is greater than that of `N`. #[inline(always)] pub fn set_len(&mut self, len: usize) { assert!(self.len <= N, "cannot set length longer than the fixed size"); self.len = len; } /// Pushes a character into the string. /// /// The internal length is updated accordingly. /// /// # Panics /// /// If the string cannot hold any more character (i.e. it is full), this method will panic. #[inline(always)] pub fn push(&mut self, c: char) { assert!(!self.is_full(), "cannot push character to full string"); self.buf[self.len] = c; self.len += 0x1; } /// Pops a character from the string. /// /// The internal length is updated accordingly. /// /// If no characters are left (i.e. the string is empty), an instance of [`None`] is returned. #[inline(always)] pub fn pop(&mut self) -> Option { self.len .checked_sub(0x1) .map(|len| { let c = self.buf[self.len]; self.len = len; c }) } } impl AsMut<[char]> for FixedString { #[inline(always)] fn as_mut(&mut self) -> &mut [char] { self.as_mut_slice() } } impl AsRef<[char]> for FixedString { #[inline(always)] fn as_ref(&self) -> &[char] { self.as_slice() } } impl Debug for FixedString { #[inline] fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { write!(f, "\"")?; for c in self { write!(f, "{}", c.escape_debug())? } write!(f, "\"")?; Ok(()) } } impl Default for FixedString { #[inline(always)] fn default() -> Self { Self { buf: [Default::default(); N], len: 0x0 } } } /// See [`as_slice`](Self::as_slice). impl Deref for FixedString { type Target = [char]; #[inline(always)] fn deref(&self) -> &Self::Target { self.as_slice() } } /// See [`as_mut_slice`](Self::as_mut_slice). impl DerefMut for FixedString { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_slice() } } impl Display for FixedString { #[inline] fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { for c in self { write!(f, "{c}")? } Ok(()) } } impl Eq for FixedString { } impl From<[char; N]> for FixedString { #[inline(always)] fn from(value: [char; N]) -> Self { Self::from_chars(value) } } impl FromStr for FixedString { type Err = Error; #[inline] fn from_str(s: &str) -> Result { let mut buf = [Default::default(); N]; let len = s.chars().count(); for (i, c) in s.chars().enumerate() { if i >= N { return Err(Error::ArrayTooShort { req: len, len: N }) } buf[i] = c; } Ok(Self { buf, len }) } } impl, const N: usize> Index for FixedString { type Output = I::Output; #[inline(always)] fn index(&self, index: I) -> &Self::Output { self.get(index).unwrap() } } impl, const N: usize> IndexMut for FixedString { #[inline(always)] fn index_mut(&mut self, index: I) -> &mut Self::Output { self.get_mut(index).unwrap() } } impl IntoIterator for FixedString { type Item = char; type IntoIter = FixedIter; #[inline(always)] fn into_iter(self) -> Self::IntoIter { FixedIter { buf: unsafe { self.buf.as_ptr().cast::<[MaybeUninit; N]>().read() }, pos: 0x0, len: self.len, } } } impl<'a, const N: usize> IntoIterator for &'a FixedString { type Item = &'a char; type IntoIter = core::slice::Iter<'a, char>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<'a, const N: usize> IntoIterator for &'a mut FixedString { type Item = &'a mut char; type IntoIter = core::slice::IterMut<'a, char>; #[inline(always)] fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl Ord for FixedString { #[inline(always)] fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap() } } impl PartialEq> for FixedString { #[inline(always)] fn eq(&self, other: &FixedString) -> bool { self.as_slice() == other.as_slice() } } impl PartialEq<&[char]> for FixedString { #[inline(always)] fn eq(&self, other: &&[char]) -> bool { self.as_slice() == *other } } impl PartialEq<&str> for FixedString { #[inline] fn eq(&self, other: &&str) -> bool { for (i, c) in other.chars().enumerate() { if self.get(i) != Some(&c) { return false }; } true } } impl PartialOrd> for FixedString { #[inline(always)] fn partial_cmp(&self, other: &FixedString) -> Option { self.partial_cmp(&other.as_slice()) } } impl PartialOrd<&[char]> for FixedString { #[inline(always)] fn partial_cmp(&self, other: &&[char]) -> Option { self.as_slice().partial_cmp(other) } } impl PartialOrd<&str> for FixedString { #[inline] fn partial_cmp(&self, other: &&str) -> Option { let llen = self.len(); let rlen = other.chars().count(); match llen.cmp(&rlen) { Ordering::Equal => {}, ordering => return Some(ordering), }; for (i, rc) in other.chars().enumerate() { let lc = self[i]; match lc.cmp(&rc) { Ordering::Equal => {}, ordering => return Some(ordering), } } Some(Ordering::Equal) } } impl TryFrom<&str> for FixedString { type Error = ::Err; #[inline(always)] fn try_from(value: &str) -> Result { Self::from_str(value) } } #[cfg(feature = "alloc")] impl TryFrom for FixedString { type Error = ::Err; #[inline(always)] fn try_from(value: String) -> Result { Self::from_str(&value) } } #[cfg(feature = "alloc")] impl From> for String { #[inline(always)] fn from(value: FixedString) -> Self { value.to_string() } }