use crate::{
Char, CharIter, InvalidText, MismatchedCapacity, NotEnoughElements, NotEnoughSpace, Str, char7,
char8, char16, charu, is, lets, slice, unwrap, whilst,
};
const NUL_CHAR: char = '\0';
#[doc = crate::_tags!(text)]
#[doc = crate::_doc_location!("text/str")]
#[cfg_attr(
feature = "unsafe_str",
doc = "[_unchecked][Self::from_array_unchecked]<sup title='unsafe function'>⚠</sup>."
)]
#[cfg_attr(
feature = "unsafe_str",
doc = "*([mut][Self::as_bytes_mut]<sup title='unsafe function'>⚠</sup>)*."
)]
#[cfg_attr(
feature = "unsafe_str",
doc = "*([mut][Self::as_mut_str]<sup title='unsafe function'>⚠</sup>)*."
)]
#[must_use]
#[derive(Clone, Copy, Eq, PartialOrd, Ord)]
pub struct StringNonul<const CAP: usize> {
arr: [u8; CAP],
}
#[rustfmt::skip]
impl<const CAP: usize> StringNonul<CAP> {
pub const fn new() -> Self {
assert![CAP <= u8::MAX as usize, "Mismatched capacity, greater than u8::MAX"];
Self { arr: [0; CAP] }
}
pub const fn new_checked() -> Result<Self, MismatchedCapacity> {
if CAP <= u8::MAX as usize {
Ok(Self { arr: [0; CAP] })
} else {
Err(MismatchedCapacity::too_large(CAP, u8::MAX as usize))
}
}
pub const fn from_str(string: &str) -> Result<Self, MismatchedCapacity> {
let mut new_string = unwrap![ok? Self::new_checked()];
if new_string.try_push_str_complete(string).is_ok() { Ok(new_string) }
else { Err(MismatchedCapacity::too_small(CAP, string.len())) }
}
pub const fn from_str_truncate(string: &str) -> Result<Self, MismatchedCapacity> {
let mut new_string = unwrap![ok? Self::new_checked()];
let _ = new_string.push_str(string);
Ok(new_string)
}
#[inline(always)]
pub const fn from_str_unchecked(string: &str) -> Self {
let mut new_string = Self::new();
let _ = new_string.push_str(string);
new_string
}
#[must_use] #[inline(always)]
pub const fn capacity() -> usize { CAP }
#[must_use] #[inline(always)]
pub const fn remaining_capacity(&self) -> usize { CAP - self.len() }
#[must_use]
pub const fn len(&self) -> usize {
let mut position = 0;
while position < CAP {
is![self.arr[position] == 0, break];
position += 1;
}
position
}
#[must_use] #[inline(always)]
pub const fn is_empty(&self) -> bool { self.len() == 0 }
#[must_use] #[inline(always)]
pub const fn is_full(&self) -> bool { self.len() == CAP }
#[must_use] #[inline(always)]
pub const fn eq(&self, other: &Self) -> bool {
let mut i = 0;
while i < CAP {
if self.arr[i] != other.arr[i] { return false; }
if self.arr[i] == 0 { return true; }
i += 1;
}
true
}
#[must_use] #[inline(always)]
pub const fn into_array(self) -> [u8; CAP] { self.arr }
#[must_use] #[inline(always)]
pub const fn as_array(&self) -> &[u8; CAP] { &self.arr }
#[must_use] #[inline(always)]
pub const fn as_bytes(&self) -> &[u8] { self.arr.split_at(self.len()).0 }
#[must_use] #[inline(always)]
#[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
#[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
pub const unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
let len = self.len();
#[cfg(any(feature = "safe_text", not(feature = "unsafe_slice")))]
return slice![mut &mut self.arr, ..len];
#[cfg(all(not(feature = "safe_text"), feature = "unsafe_slice"))]
unsafe { slice![mut_unchecked &mut self.arr, ..len] }
}
#[must_use]
pub const fn as_str(&self) -> &str {
#[cfg(any(feature = "safe_text", not(feature = "unsafe_slice")))]
return unwrap![ok_expect Str::from_utf8(self.as_bytes()), "Invalid UTF-8"];
#[cfg(all(not(feature = "safe_text"), feature = "unsafe_slice"))]
unsafe { Str::from_utf8_unchecked(self.as_bytes()) }
}
#[must_use]
#[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
#[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
pub const unsafe fn as_mut_str(&mut self) -> &mut str {
unsafe { Str::from_utf8_unchecked_mut(self.as_bytes_mut()) }
}
#[inline(always)]
pub const fn chars(&self) -> CharIter<'_, &str> { CharIter::<&str>::new(self.as_str()) }
pub const fn clear(&mut self) { self.arr = [0; CAP]; }
#[must_use]
pub const fn pop(&mut self) -> Option<char> {
if self.is_empty() { None } else { Some(self.pop_unchecked()) }
}
pub const fn try_pop(&mut self) -> Result<char, NotEnoughElements> {
if self.is_empty() {
Err(NotEnoughElements(Some(1)))
} else {
Ok(self.pop_unchecked())
}
}
#[must_use] #[rustfmt::skip]
pub const fn pop_unchecked(&mut self) -> char {
let (len, string) = (self.len(), self.as_str());
let mut idx_last_char = len - 1;
while idx_last_char > 0 && !string.is_char_boundary(idx_last_char) { idx_last_char -= 1; }
let range = Str::range(string, idx_last_char, len);
let last_char = unwrap![some CharIter::<&str>::new(range).next_char()];
let mut i = idx_last_char; while i < len { self.arr[i] = 0; i += 1; } last_char
}
pub const fn push(&mut self, character: char) -> usize {
let char_len = character.len_utf8();
if character != NUL_CHAR && self.remaining_capacity() >= char_len {
let len = self.len();
let _ = character.encode_utf8(slice![mut &mut self.arr, len, ..len + char_len]);
char_len
} else {
0
}
}
pub const fn try_push(&mut self, character: char) -> Result<usize, NotEnoughSpace> {
let char_len = character.len_utf8();
if character == NUL_CHAR {
Ok(0)
} else if self.remaining_capacity() >= char_len {
let len = self.len();
let _ = character.encode_utf8(slice![mut &mut self.arr, len, ..len + char_len]);
Ok(char_len)
} else {
Err(NotEnoughSpace(Some(char_len)))
}
}
pub const fn push_str(&mut self, string: &str) -> usize {
let mut rem_cap = self.remaining_capacity();
let mut bytes_written = 0;
let mut chars = CharIter::<&str>::new(string);
while let Some(character) = chars.next_char() {
if character != NUL_CHAR {
let char_len = character.len_utf8();
if char_len <= rem_cap {
self.push(character);
rem_cap -= char_len;
bytes_written += char_len;
} else {
break;
}
}
}
bytes_written
}
pub const fn try_push_str(&mut self, string: &str) -> Result<usize, NotEnoughSpace> {
let mut first_char_len = 0;
let mut chars = CharIter::<&str>::new(string);
while let Some(c) = chars.next_scalar() { if c != NUL_CHAR as u32 { first_char_len = Char(c).len_utf8_unchecked(); break; }
}
if self.remaining_capacity() < first_char_len {
Err(NotEnoughSpace(Some(first_char_len)))
} else {
Ok(self.push_str(string))
}
}
pub const fn try_push_str_complete(&mut self, string: &str) -> Result<usize, NotEnoughSpace> {
lets![mut non_nul_len=0, bytes=string.as_bytes(), str_len=bytes.len()];
whilst!{ i in 0..str_len; { is![bytes[i] != 0, non_nul_len += 1] }} if self.remaining_capacity() >= non_nul_len {
Ok(self.push_str(string))
} else {
Err(NotEnoughSpace(Some(str_len)))
}
}
pub const fn from_char(c: char) -> Result<Self, MismatchedCapacity> {
let mut new = unwrap![ok? Self::new_checked()];
if c != '\0' {
let bytes = Char(c).to_utf8_bytes();
let len = Char(bytes[0]).len_utf8_unchecked();
is![CAP < len, return Err(MismatchedCapacity::too_small(CAP, len))];
new.arr[0] = bytes[0];
if len > 1 { new.arr[1] = bytes[1]; }
if len > 2 { new.arr[2] = bytes[2]; }
if len > 3 { new.arr[3] = bytes[3]; }
}
Ok(new)
}
pub const fn from_char7(c: char7) -> Result<Self, MismatchedCapacity> {
is![CAP == 0, return Err(MismatchedCapacity::too_small(CAP, 1))];
let mut new = unwrap![ok? Self::new_checked()];
if !c.is_nul() {
new.arr[0] = c.to_utf8_bytes()[0];
}
Ok(new)
}
pub const fn from_char8(c: char8) -> Result<Self, MismatchedCapacity> {
let mut new = unwrap![ok? Self::new_checked()];
if !c.is_nul() {
let bytes = c.to_utf8_bytes();
let len = Char(bytes[0]).len_utf8_unchecked();
is![CAP < len, return Err(MismatchedCapacity::too_small(CAP, len))];
new.arr[0] = bytes[0];
if len > 1 { new.arr[1] = bytes[1]; }
}
Ok(new)
}
pub const fn from_char16(c: char16) -> Result<Self, MismatchedCapacity> {
let mut new = unwrap![ok? Self::new_checked()];
if !c.is_nul() {
let bytes = c.to_utf8_bytes();
let len = Char(bytes[0]).len_utf8_unchecked();
is![CAP < len, return Err(MismatchedCapacity::too_small(CAP, len))];
new.arr[0] = bytes[0];
if len > 1 { new.arr[1] = bytes[1]; }
if len > 2 { new.arr[2] = bytes[2]; }
}
Ok(new)
}
pub const fn from_charu(c: charu) -> Result<Self, MismatchedCapacity> {
let mut new = unwrap![ok? Self::new_checked()];
if !c.is_nul() {
let len = c.len_utf8();
if len <= CAP {
let bytes = c.to_utf8_bytes();
slice![mut &mut new.arr, 0,..len].copy_from_slice(slice![&bytes, 0,..len]);
} else {
return Err(MismatchedCapacity::too_small(CAP, len));
}
}
Ok(new)
}
pub const fn from_charu_unchecked(c: charu) -> Self {
let mut new = Self::new();
if !c.is_nul() {
let len = c.len_utf8();
let bytes = c.to_utf8_bytes();
slice![mut &mut new.arr, 0,..len].copy_from_slice(slice![&bytes, 0,..len]);
}
new
}
#[inline(always)]
pub const fn from_array(bytes: [u8; CAP]) -> Result<Self, InvalidText> {
match ::core::str::from_utf8(&bytes) {
Ok(_) => Ok(Self { arr: bytes }),
Err(e) => Err(InvalidText::from_utf8_error(e)),
}
}
#[inline(always)]
#[cfg(all(not(feature = "safe_text"), feature = "unsafe_str"))]
#[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_str")))]
pub const unsafe fn from_array_unchecked(bytes: [u8; CAP]) -> Self {
Self { arr: bytes }
}
#[inline(always)]
pub(crate) const fn _from_array_trusted(array: [u8; CAP]) -> Self {
Self { arr: array }
}
}
#[rustfmt::skip]
mod trait_impls {
use crate::{
ConstInit, Debug, Deref, Display, FmtError, FmtResult, FmtWrite, Formatter, Hash,
Hasher,
};
use super::{StringNonul, InvalidText, MismatchedCapacity, is};
impl<const CAP: usize> Default for StringNonul<CAP> {
fn default() -> Self { Self::new() }
}
impl<const CAP: usize> ConstInit for StringNonul<CAP> {
const INIT: Self = Self::new();
}
impl<const CAP: usize> Display for StringNonul<CAP> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
write!(f, "{}", self.as_str())
}
}
impl<const CAP: usize> Debug for StringNonul<CAP> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
write!(f, "{:?}", self.as_str())
}
}
impl<const CAP: usize> FmtWrite for StringNonul<CAP> {
fn write_str(&mut self, s: &str) -> FmtResult<()> {
self.try_push_str(s).map_err(|_| FmtError)?;
Ok(())
}
fn write_char(&mut self, c: char) -> FmtResult<()> {
self.try_push(c).map_err(|_| FmtError)?;
Ok(())
}
}
impl<const CAP: usize> PartialEq for StringNonul<CAP> {
fn eq(&self, other: &Self) -> bool { self.eq(other) }
}
impl<const CAP: usize> PartialEq<str> for StringNonul<CAP> { fn eq(&self, slice: &str) -> bool { self.as_str() == slice }
}
impl<const CAP: usize> PartialEq<&str> for StringNonul<CAP> { fn eq(&self, slice: &&str) -> bool { self.as_str() == *slice }
}
impl<const CAP: usize> PartialEq<&[u8]> for StringNonul<CAP> { fn eq(&self, bytes: &&[u8]) -> bool { self.as_bytes() == *bytes }
}
impl<const CAP: usize> PartialEq<StringNonul<CAP>> for str { fn eq(&self, string: &StringNonul<CAP>) -> bool { self == string.as_str() }
}
impl<const CAP: usize> PartialEq<StringNonul<CAP>> for &str { fn eq(&self, string: &StringNonul<CAP>) -> bool { *self == string.as_str() }
}
impl<const CAP: usize> PartialEq<StringNonul<CAP>> for &[u8] { fn eq(&self, string: &StringNonul<CAP>) -> bool { *self == string.as_bytes() }
}
impl<const CAP: usize> Hash for StringNonul<CAP> {
fn hash<H: Hasher>(&self, state: &mut H) {
let len = self.len();
self.arr[..len].hash(state);
}
}
impl<const CAP: usize> Deref for StringNonul<CAP> {
type Target = str;
fn deref(&self) -> &Self::Target { self.as_str() }
}
impl<const CAP: usize> AsRef<str> for StringNonul<CAP> {
fn as_ref(&self) -> &str { self.as_str() }
}
impl<const CAP: usize> AsRef<[u8]> for StringNonul<CAP> {
fn as_ref(&self) -> &[u8] { self.as_bytes() }
}
impl<const CAP: usize> TryFrom<&str> for StringNonul<CAP> {
type Error = MismatchedCapacity;
fn try_from(string: &str) -> Result<Self, MismatchedCapacity> {
let non_nul_len = string.as_bytes().iter().filter(|x| **x != 0).count();
if CAP < non_nul_len {
Err(MismatchedCapacity::too_small(CAP, non_nul_len))
} else {
let mut new_string = Self::new_checked()?;
let copied_bytes = new_string.push_str(string);
debug_assert_eq![non_nul_len, copied_bytes];
Ok(new_string)
}
}
}
impl<const CAP: usize> TryFrom<&[u8]> for StringNonul<CAP> {
type Error = InvalidText;
fn try_from(bytes: &[u8]) -> Result<Self, InvalidText> {
let len = bytes.len();
if len >= CAP { return Err(MismatchedCapacity::too_small(CAP, len).into()); }
match ::core::str::from_utf8(bytes) {
Ok(_) => {
let mut arr = [0; CAP];
let mut idx = 0;
for &byte in bytes.iter() {
if byte != 0 {
arr[idx] = byte;
idx += 1;
}
}
Ok(Self { arr })
}
Err(e) => Err(InvalidText::from_utf8_error(e)),
}
}
}
impl<const CAP: usize> Extend<char> for StringNonul<CAP> {
fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
for i in iter {
is![self.push(i) == 0, break];
}
}
}
impl<const CAP: usize> FromIterator<char> for StringNonul<CAP> {
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
let mut string = StringNonul::new();
string.extend(iter);
string
}
}
}