mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-10 04:07:06 +00:00
Improved AsciiStrArr.
This commit is contained in:
@@ -6,7 +6,8 @@ pub mod slice;
|
||||
mod visitor;
|
||||
|
||||
// Exports
|
||||
pub use error::{FromByteStringError, NotAsciiError, TooLongError};
|
||||
pub use error::{FromBytesError, NotAsciiError, TooLongError};
|
||||
pub use slice::SliceIndex;
|
||||
|
||||
// Imports
|
||||
use ascii::{AsciiChar, AsciiStr};
|
||||
@@ -20,96 +21,178 @@ pub struct AsciiStrArr<const N: usize> {
|
||||
chars: [AsciiChar; N],
|
||||
|
||||
/// Size
|
||||
///
|
||||
/// Invariant: Must be `< N`
|
||||
// Invariant `self.len <= N`
|
||||
len: usize,
|
||||
}
|
||||
|
||||
// Length interface
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Returns the length of this string
|
||||
#[must_use]
|
||||
pub const fn len(&self) -> usize {
|
||||
#[contracts::debug_ensures(self.len <= N)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns if this string is empty
|
||||
#[must_use]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this string to a `&[AsciiStr]`
|
||||
// Conversions into other strings
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Converts this string to a `&AsciiStr`
|
||||
#[must_use]
|
||||
pub fn as_ascii_str(&self) -> &AsciiStr {
|
||||
// Note: Cannot panic due to our invariant
|
||||
<&AsciiStr>::from(&self.chars[..self.len])
|
||||
pub fn as_ascii(&self) -> &AsciiStr {
|
||||
// SAFETY: `self.len <= N`
|
||||
let chars = unsafe { self.chars.get_unchecked(..self.len()) };
|
||||
chars.into()
|
||||
}
|
||||
|
||||
/// Converts this string to a `&mut [AsciiStr]`
|
||||
/// Converts this string to a `&mut AsciiStr`
|
||||
#[must_use]
|
||||
pub fn as_ascii_str_mut(&mut self) -> &mut AsciiStr {
|
||||
// Note: Cannot panic due to our invariant
|
||||
<&mut AsciiStr>::from(&mut self.chars[..self.len])
|
||||
pub fn as_ascii_mut(&mut self) -> &mut AsciiStr {
|
||||
// SAFETY: `self.len <= N`
|
||||
let len = self.len();
|
||||
let chars = unsafe { self.chars.get_unchecked_mut(..len) };
|
||||
chars.into()
|
||||
}
|
||||
|
||||
/// Converts this string to a `&[u8]`
|
||||
#[must_use]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.as_ascii_str().as_bytes()
|
||||
self.as_ascii().as_bytes()
|
||||
}
|
||||
|
||||
/// Converts this string to a `&str`
|
||||
#[must_use]
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.as_ascii_str().as_str()
|
||||
self.as_ascii().as_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversions from other strings
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Creates a string from a `&AsciiStr`
|
||||
pub fn from_ascii(ascii: &AsciiStr) -> Result<Self, TooLongError<N>> {
|
||||
// If we can't fit it, return Err
|
||||
let len = ascii.len();
|
||||
if len > N {
|
||||
return Err(TooLongError::<N>);
|
||||
}
|
||||
|
||||
// Else copy everything over and return ourselves
|
||||
let mut chars = [AsciiChar::Null; N];
|
||||
chars[0..len].copy_from_slice(ascii.as_ref());
|
||||
Ok(Self { chars, len })
|
||||
}
|
||||
|
||||
/// Creates a string from a `&[u8]`
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, FromBytesError<N>> {
|
||||
// Get the bytes as ascii first
|
||||
let ascii = AsciiStr::from_ascii(bytes).map_err(FromBytesError::NotAscii)?;
|
||||
|
||||
// Then try to convert them
|
||||
Self::from_ascii(ascii).map_err(FromBytesError::TooLong)
|
||||
}
|
||||
|
||||
// Note: No `from_str`, implemented using `FromStr`
|
||||
}
|
||||
|
||||
// Slicing
|
||||
impl<const N: usize> AsciiStrArr<N> {
|
||||
/// Slices this string, if in bounds
|
||||
#[must_use]
|
||||
pub fn get<I: SliceIndex>(&self, idx: I) -> Option<&I::Output> {
|
||||
idx.get(self)
|
||||
}
|
||||
|
||||
/// Slices this string mutably, if in bounds
|
||||
#[must_use]
|
||||
pub fn get_mut<I: SliceIndex>(&mut self, idx: I) -> Option<&mut I::Output> {
|
||||
idx.get_mut(self)
|
||||
}
|
||||
|
||||
/// Slices the string without checking bounds
|
||||
///
|
||||
/// # Safety
|
||||
/// Calling this method with an out-of-bounds index is undefined-behavior even if the resulting
|
||||
/// reference is not used
|
||||
#[must_use]
|
||||
pub unsafe fn get_unchecked<I: SliceIndex>(&self, idx: I) -> &I::Output {
|
||||
idx.get_unchecked(self)
|
||||
}
|
||||
|
||||
/// Slices the string mutably without checking bounds
|
||||
///
|
||||
/// # Safety
|
||||
/// Calling this method with an out-of-bounds index is undefined-behavior even if the resulting
|
||||
/// reference is not used
|
||||
#[must_use]
|
||||
pub unsafe fn get_unchecked_mut<I: SliceIndex>(&mut self, idx: I) -> &mut I::Output {
|
||||
idx.get_unchecked_mut(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<AsciiStr> for AsciiStrArr<N> {
|
||||
fn as_ref(&self) -> &AsciiStr {
|
||||
self.as_ascii_str()
|
||||
self.as_ascii()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsMut<AsciiStr> for AsciiStrArr<N> {
|
||||
fn as_mut(&mut self) -> &mut AsciiStr {
|
||||
self.as_ascii_str_mut()
|
||||
self.as_ascii_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<[u8]> for AsciiStrArr<N> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<str> for AsciiStrArr<N> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> PartialEq for AsciiStrArr<N> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
AsciiStr::eq(self.as_ascii_str(), other.as_ascii_str())
|
||||
AsciiStr::eq(self.as_ascii(), other.as_ascii())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> PartialOrd for AsciiStrArr<N> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
AsciiStr::partial_cmp(self.as_ascii_str(), other.as_ascii_str())
|
||||
AsciiStr::partial_cmp(self.as_ascii(), other.as_ascii())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Ord for AsciiStrArr<N> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
AsciiStr::cmp(self.as_ascii_str(), other.as_ascii_str())
|
||||
AsciiStr::cmp(self.as_ascii(), other.as_ascii())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Hash for AsciiStrArr<N> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
AsciiStr::hash(self.as_ascii_str(), state)
|
||||
AsciiStr::hash(self.as_ascii(), state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for AsciiStrArr<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
AsciiStr::fmt(self.as_ascii_str(), f)
|
||||
AsciiStr::fmt(self.as_ascii(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Display for AsciiStrArr<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
AsciiStr::fmt(self.as_ascii_str(), f)
|
||||
AsciiStr::fmt(self.as_ascii(), f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,16 +213,14 @@ impl<const N: usize> serde::Serialize for AsciiStrArr<N> {
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_ascii_str().as_str())
|
||||
serializer.serialize_str(self.as_ascii().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Generalize this to `impl<const N: usize, const M: usize> From<&[AsciiChar; M]> for AsciiStrArr<N> where M <= N`
|
||||
impl<const N: usize> From<&[AsciiChar; N]> for AsciiStrArr<N> {
|
||||
fn from(src: &[AsciiChar; N]) -> Self {
|
||||
let mut chars = [AsciiChar::Null; N];
|
||||
chars.copy_from_slice(src);
|
||||
Self { chars, len: N }
|
||||
Self::from(*src)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,35 +248,34 @@ impl<const N: usize> TryFrom<&[u8; N]> for AsciiStrArr<N> {
|
||||
impl<const N: usize> TryFrom<&AsciiStr> for AsciiStrArr<N> {
|
||||
type Error = TooLongError<N>;
|
||||
|
||||
fn try_from(ascii_str: &AsciiStr) -> Result<Self, Self::Error> {
|
||||
// Note: No space for null, this isn't null terminated
|
||||
let len = ascii_str.len();
|
||||
if len > N {
|
||||
return Err(TooLongError::<N>);
|
||||
}
|
||||
|
||||
let mut chars = [AsciiChar::Null; N];
|
||||
chars[0..len].copy_from_slice(ascii_str.as_ref());
|
||||
Ok(Self { chars, len })
|
||||
fn try_from(ascii: &AsciiStr) -> Result<Self, Self::Error> {
|
||||
Self::from_ascii(ascii)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> TryFrom<&[u8]> for AsciiStrArr<N> {
|
||||
type Error = FromByteStringError<N>;
|
||||
type Error = FromBytesError<N>;
|
||||
|
||||
fn try_from(byte_str: &[u8]) -> Result<Self, Self::Error> {
|
||||
let ascii_str = AsciiStr::from_ascii(byte_str).map_err(FromByteStringError::NotAscii)?;
|
||||
|
||||
Self::try_from(ascii_str).map_err(FromByteStringError::TooLong)
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> TryFrom<&str> for AsciiStrArr<N> {
|
||||
type Error = FromByteStringError<N>;
|
||||
type Error = FromBytesError<N>;
|
||||
|
||||
fn try_from(string: &str) -> Result<Self, Self::Error> {
|
||||
let ascii_str = AsciiStr::from_ascii(string).map_err(FromByteStringError::NotAscii)?;
|
||||
let ascii_str = AsciiStr::from_ascii(string).map_err(FromBytesError::NotAscii)?;
|
||||
|
||||
Self::try_from(ascii_str).map_err(FromByteStringError::TooLong)
|
||||
Self::try_from(ascii_str).map_err(FromBytesError::TooLong)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> std::str::FromStr for AsciiStrArr<N> {
|
||||
type Err = FromBytesError<N>;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// Simply delegate to [`Self::from_bytes`]
|
||||
Self::from_bytes(s.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ pub struct NotAsciiError {
|
||||
pub pos: usize,
|
||||
}
|
||||
|
||||
/// Error returned from [`TryFrom<&[u8]> for AsciiStrArr<N>`]
|
||||
/// Error returned when converting a `&[u8]` to a `AsciiStrArr`
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum FromByteStringError<const LEN: usize> {
|
||||
pub enum FromBytesError<const LEN: usize> {
|
||||
/// Too long
|
||||
#[error("String was too long")]
|
||||
TooLong(TooLongError<LEN>),
|
||||
|
||||
@@ -7,40 +7,59 @@ use std::ops::{Index, IndexMut};
|
||||
|
||||
/// Helper trait for slicing a [`AsciiStrArr`]
|
||||
// Note: Adapted from `std::slice::SliceIndex`
|
||||
pub trait SliceIndex<I>
|
||||
where
|
||||
I: ?Sized,
|
||||
{
|
||||
pub trait SliceIndex {
|
||||
/// Output type for this slicing
|
||||
type Output: ?Sized;
|
||||
|
||||
/// Tries to get the `idx`th element
|
||||
fn get(&self, idx: I) -> Option<&Self::Output>;
|
||||
/// Slices the string, if in bounds
|
||||
fn get<const N: usize>(self, ascii_str: &AsciiStrArr<N>) -> Option<&Self::Output>;
|
||||
|
||||
/// Tries to get the `idx`th element mutably
|
||||
fn get_mut(&mut self, idx: I) -> Option<&mut Self::Output>;
|
||||
/// Slices the string mutably, if in bounds
|
||||
fn get_mut<const N: usize>(self, ascii_str: &mut AsciiStrArr<N>) -> Option<&mut Self::Output>;
|
||||
|
||||
/// Slices the string without checking bounds
|
||||
///
|
||||
/// # Safety
|
||||
/// Calling this method with an out-of-bounds index is undefined-behavior even if the resulting
|
||||
/// reference is not used
|
||||
unsafe fn get_unchecked<const N: usize>(self, ascii_str: &AsciiStrArr<N>) -> &Self::Output;
|
||||
|
||||
/// Slices the string mutably without checking bounds
|
||||
///
|
||||
/// # Safety
|
||||
/// Calling this method with an out-of-bounds index is undefined-behavior even if the resulting
|
||||
/// reference is not used
|
||||
unsafe fn get_unchecked_mut<const N: usize>(self, ascii_str: &mut AsciiStrArr<N>) -> &mut Self::Output;
|
||||
}
|
||||
|
||||
impl<I, const N: usize> SliceIndex<I> for AsciiStrArr<N>
|
||||
impl<I> SliceIndex for I
|
||||
where
|
||||
I: std::slice::SliceIndex<[AsciiChar]>,
|
||||
{
|
||||
type Output = <I as std::slice::SliceIndex<[AsciiChar]>>::Output;
|
||||
type Output = <Self as std::slice::SliceIndex<[AsciiChar]>>::Output;
|
||||
|
||||
fn get(&self, idx: I) -> Option<&Self::Output> {
|
||||
self.as_ascii_str().as_slice().get(idx)
|
||||
fn get<const N: usize>(self, ascii_str: &AsciiStrArr<N>) -> Option<&Self::Output> {
|
||||
ascii_str.as_ascii().as_slice().get(self)
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, idx: I) -> Option<&mut Self::Output> {
|
||||
self.as_ascii_str_mut().as_mut_slice().get_mut(idx)
|
||||
fn get_mut<const N: usize>(self, ascii_str: &mut AsciiStrArr<N>) -> Option<&mut Self::Output> {
|
||||
ascii_str.as_ascii_mut().as_mut_slice().get_mut(self)
|
||||
}
|
||||
|
||||
unsafe fn get_unchecked<const N: usize>(self, ascii_str: &AsciiStrArr<N>) -> &Self::Output {
|
||||
ascii_str.as_ascii().as_slice().get_unchecked(self)
|
||||
}
|
||||
|
||||
unsafe fn get_unchecked_mut<const N: usize>(self, ascii_str: &mut AsciiStrArr<N>) -> &mut Self::Output {
|
||||
ascii_str.as_ascii_mut().as_mut_slice().get_unchecked_mut(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, const N: usize> Index<I> for AsciiStrArr<N>
|
||||
where
|
||||
Self: SliceIndex<I>,
|
||||
I: SliceIndex,
|
||||
{
|
||||
type Output = <Self as SliceIndex<I>>::Output;
|
||||
type Output = <I as SliceIndex>::Output;
|
||||
|
||||
fn index(&self, idx: I) -> &Self::Output {
|
||||
self.get(idx).expect("Invalid index access")
|
||||
@@ -49,7 +68,7 @@ where
|
||||
|
||||
impl<I, const N: usize> IndexMut<I> for AsciiStrArr<N>
|
||||
where
|
||||
Self: SliceIndex<I>,
|
||||
I: SliceIndex,
|
||||
{
|
||||
fn index_mut(&mut self, idx: I) -> &mut Self::Output {
|
||||
self.get_mut(idx).expect("Invalid index access")
|
||||
|
||||
Reference in New Issue
Block a user