use alloc::borrow::Cow;
use alloc::fmt;
use alloc::string::{FromUtf16Error, String};
use core::borrow::Borrow;
use core::error::Error;
use core::hash::Hash;
use core::mem::transmute;
use core::ops::{Deref, DerefMut, Range, RangeBounds};
use core::str::{Lines, SplitAsciiWhitespace, SplitWhitespace, Utf8Error};
use self::pattern::{DoubleEndedPattern, IterWrapper, Pattern, ReversePattern};
use crate::bytes::{simplify_range, HipByt, SliceErrorKind as ByteSliceErrorKind};
use crate::Backend;
mod cmp;
mod convert;
mod pattern;
#[cfg(feature = "borsh")]
mod borsh;
#[cfg(feature = "bstr")]
pub(crate) mod bstr;
#[cfg(feature = "serde")]
pub mod serde;
#[cfg(test)]
mod tests;
#[repr(transparent)]
pub struct HipStr<'borrow, B>(HipByt<'borrow, B>)
where
B: Backend;
impl<'borrow, B> HipStr<'borrow, B>
where
B: Backend,
{
#[inline]
#[must_use]
pub const fn new() -> Self {
Self(HipByt::new())
}
#[inline]
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self(HipByt::with_capacity(cap))
}
#[inline]
#[must_use]
pub const fn borrowed(value: &'borrow str) -> Self {
Self(HipByt::borrowed(value.as_bytes()))
}
#[inline]
#[must_use]
pub const fn is_inline(&self) -> bool {
self.0.is_inline()
}
#[inline]
#[must_use]
pub const fn is_borrowed(&self) -> bool {
self.0.is_borrowed()
}
#[inline]
#[must_use]
pub const fn is_allocated(&self) -> bool {
self.0.is_allocated()
}
#[inline]
pub fn into_borrowed(self) -> Result<&'borrow str, Self> {
self.0
.into_borrowed()
.map(|slice|
unsafe { core::str::from_utf8_unchecked(slice) })
.map_err(Self)
}
#[inline]
#[must_use]
pub const fn as_borrowed(&self) -> Option<&'borrow str> {
match self.0.as_borrowed() {
Some(slice) => Some(unsafe {
core::str::from_utf8_unchecked(slice)
}),
None => None,
}
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.0.len()
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
#[must_use]
pub const fn as_ptr(&self) -> *const u8 {
self.0.as_ptr()
}
#[inline]
#[must_use]
pub fn as_mut_ptr(&mut self) -> Option<*mut u8> {
self.0.as_mut_ptr()
}
#[inline]
#[must_use]
pub unsafe fn as_mut_ptr_unchecked(&mut self) -> *mut u8 {
unsafe { self.0.as_mut_ptr_unchecked() }
}
#[must_use]
pub fn into_bytes(self) -> HipByt<'borrow, B> {
self.0
}
#[inline]
#[must_use]
pub const fn as_str(&self) -> &str {
let slice = self.0.as_slice();
unsafe { core::str::from_utf8_unchecked(slice) }
}
#[inline]
#[must_use]
pub fn as_mut_str(&mut self) -> Option<&mut str> {
self.0.as_mut_slice().map(|slice|
unsafe { core::str::from_utf8_unchecked_mut(slice) })
}
#[inline]
#[doc(alias = "make_mut")]
pub fn to_mut_str(&mut self) -> &mut str {
let slice = self.0.to_mut_slice();
unsafe { core::str::from_utf8_unchecked_mut(slice) }
}
#[must_use]
#[track_caller]
pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
match self.try_slice(range) {
Ok(result) => result,
Err(err) => panic!("{}", err),
}
}
pub fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, SliceError<B>> {
let range = simplify_range(range, self.len())
.map_err(|(start, end, kind)| SliceError::new(kind, start, end, self))?;
if !self.is_char_boundary(range.start) {
return Err(SliceError {
kind: SliceErrorKind::StartNotACharBoundary,
start: range.start,
end: range.end,
string: self,
});
}
if !self.is_char_boundary(range.end) {
return Err(SliceError {
kind: SliceErrorKind::EndNotACharBoundary,
start: range.start,
end: range.end,
string: self,
});
}
Ok(unsafe { self.slice_unchecked(range) })
}
#[must_use]
pub unsafe fn slice_unchecked(&self, range: impl RangeBounds<usize>) -> Self {
#[cfg(debug_assertions)]
{
let range =
simplify_range((range.start_bound(), range.end_bound()), self.len()).unwrap();
assert!(self.is_char_boundary(range.start));
assert!(self.is_char_boundary(range.end));
}
Self(unsafe { self.0.slice_unchecked(range) })
}
#[must_use]
#[track_caller]
pub fn slice_ref(&self, slice: &str) -> Self {
let Some(result) = self.try_slice_ref(slice) else {
panic!("slice {slice:p} is not a part of {self:p}")
};
result
}
#[must_use]
pub unsafe fn slice_ref_unchecked(&self, slice: &str) -> Self {
let slice = slice.as_bytes();
unsafe { Self(self.0.slice_ref_unchecked(slice)) }
}
pub fn try_slice_ref(&self, range: &str) -> Option<Self> {
self.0.try_slice_ref(range.as_bytes()).map(Self)
}
#[inline]
pub fn from_utf16(v: &[u16]) -> Result<Self, FromUtf16Error> {
String::from_utf16(v).map(Into::into)
}
#[inline]
#[must_use]
pub fn from_utf16_lossy(v: &[u16]) -> Self {
String::from_utf16_lossy(v).into()
}
#[inline]
#[must_use]
pub const unsafe fn from_utf8_unchecked(byt: HipByt<'borrow, B>) -> Self {
Self(byt)
}
#[inline]
pub const fn from_utf8(bytes: HipByt<'borrow, B>) -> Result<Self, FromUtf8Error<'borrow, B>> {
match core::str::from_utf8(bytes.as_slice()) {
Ok(_) => {
Ok(unsafe { Self::from_utf8_unchecked(bytes) })
}
Err(e) => Err(FromUtf8Error { bytes, error: e }),
}
}
#[inline]
#[must_use]
pub fn from_utf8_lossy(bytes: HipByt<'borrow, B>) -> Self {
match String::from_utf8_lossy(&bytes) {
Cow::Borrowed(_) => Self(bytes),
Cow::Owned(s) => Self::from(s),
}
}
#[inline]
#[must_use]
pub const fn inline_capacity() -> usize {
HipByt::<B>::inline_capacity()
}
#[inline]
#[must_use]
pub fn capacity(&self) -> usize {
self.0.capacity()
}
#[inline]
pub fn into_string(self) -> Result<String, Self> {
self.0
.into_vec()
.map(|v| unsafe { String::from_utf8_unchecked(v) })
.map_err(Self)
}
#[inline]
#[must_use]
pub fn mutate(&mut self) -> RefMut<'_, 'borrow, B> {
let vec = self.0.take_vec();
let owned = unsafe { String::from_utf8_unchecked(vec) };
RefMut {
result: self,
owned,
}
}
#[inline]
pub fn truncate(&mut self, new_len: usize) {
if new_len <= self.len() {
assert!(self.is_char_boundary(new_len), "char boundary");
self.0.truncate(new_len);
}
}
#[inline]
pub fn clear(&mut self) {
self.0.clear();
}
#[inline]
pub fn shrink_to_fit(&mut self) {
self.0.shrink_to_fit();
}
#[inline]
pub fn shrink_to(&mut self, min_capacity: usize) {
self.0.shrink_to(min_capacity);
}
#[inline]
pub fn pop(&mut self) -> Option<char> {
let (i, ch) = self.as_str().char_indices().next_back()?;
self.truncate(i);
Some(ch)
}
#[inline]
pub fn push_str(&mut self, addition: &str) {
self.0.push_slice(addition.as_bytes());
}
#[inline]
pub fn push(&mut self, ch: char) {
let mut data = [0; 4];
let s = ch.encode_utf8(&mut data);
self.0.push_slice(s.as_bytes());
}
#[must_use]
pub fn into_owned(self) -> HipStr<'static, B> {
HipStr(self.0.into_owned())
}
#[inline]
#[must_use]
pub fn to_ascii_lowercase(&self) -> Self {
Self(self.0.to_ascii_lowercase())
}
#[inline]
#[must_use]
pub fn to_ascii_uppercase(&self) -> Self {
Self(self.0.to_ascii_uppercase())
}
#[inline]
pub fn make_ascii_uppercase(&mut self) {
self.0.make_ascii_uppercase();
}
#[inline]
pub fn make_ascii_lowercase(&mut self) {
self.0.make_ascii_lowercase();
}
#[inline]
#[must_use]
pub fn to_lowercase(&self) -> Self {
Self::from(self.as_str().to_lowercase())
}
#[inline]
#[must_use]
pub fn to_uppercase(&self) -> Self {
Self::from(self.as_str().to_uppercase())
}
#[inline]
#[must_use]
pub fn repeat(&self, n: usize) -> Self {
Self(self.0.repeat(n))
}
#[inline]
#[must_use]
pub fn trim(&self) -> Self {
let s = self.as_str().trim();
unsafe { self.slice_ref_unchecked(s) }
}
#[inline]
#[must_use]
pub fn trim_start(&self) -> Self {
let s = self.as_str().trim_start();
unsafe { self.slice_ref_unchecked(s) }
}
#[inline]
#[must_use]
pub fn trim_end(&self) -> Self {
let s = self.as_str().trim_end();
unsafe { self.slice_ref_unchecked(s) }
}
#[inline]
pub fn split<P: Pattern>(&self, pattern: P) -> IterWrapper<'_, 'borrow, B, P::Split<'_>> {
IterWrapper::new(self, pattern.split(self.as_str()))
}
#[inline]
pub fn split_inclusive<P: Pattern>(
&self,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::SplitInclusive<'_>> {
IterWrapper::new(self, pattern.split_inclusive(self.as_str()))
}
#[inline]
pub fn rsplit<P: ReversePattern>(
&self,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::RSplit<'_>> {
IterWrapper::new(self, pattern.rsplit(self.as_str()))
}
#[inline]
pub fn split_terminator<P: Pattern>(
&self,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::SplitTerminator<'_>> {
IterWrapper::new(self, pattern.split_terminator(self.as_str()))
}
#[inline]
pub fn rsplit_terminator<P: ReversePattern>(
&self,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::RSplitTerminator<'_>> {
IterWrapper::new(self, pattern.rsplit_terminator(self.as_str()))
}
#[inline]
pub fn splitn<P: Pattern>(
&self,
n: usize,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::SplitN<'_>> {
IterWrapper::new(self, pattern.splitn(n, self.as_str()))
}
#[inline]
pub fn rsplitn<P: ReversePattern>(
&self,
n: usize,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::RSplitN<'_>> {
IterWrapper::new(self, pattern.rsplitn(n, self.as_str()))
}
#[inline]
pub fn split_once<P: Pattern>(&self, pattern: P) -> Option<(Self, Self)> {
pattern
.split_once(self.as_str())
.map(|(a, b)| unsafe { (self.slice_ref_unchecked(a), self.slice_ref_unchecked(b)) })
}
#[inline]
pub fn rsplit_once<P: ReversePattern>(&self, pattern: P) -> Option<(Self, Self)> {
pattern
.rsplit_once(self.as_str())
.map(|(a, b)| unsafe { (self.slice_ref_unchecked(a), self.slice_ref_unchecked(b)) })
}
#[inline]
pub fn matches<P: Pattern>(&self, pattern: P) -> IterWrapper<'_, 'borrow, B, P::Matches<'_>> {
IterWrapper::new(self, pattern.matches(self.as_str()))
}
#[inline]
pub fn rmatches<P: ReversePattern>(
&self,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::RMatches<'_>> {
IterWrapper::new(self, pattern.rmatches(self.as_str()))
}
#[inline]
pub fn match_indices<P: Pattern>(
&self,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::MatchIndices<'_>> {
IterWrapper::new(self, pattern.match_indices(self.as_str()))
}
#[inline]
pub fn rmatch_indices<P: ReversePattern>(
&self,
pattern: P,
) -> IterWrapper<'_, 'borrow, B, P::RMatchIndices<'_>> {
IterWrapper::new(self, pattern.rmatch_indices(self.as_str()))
}
#[inline]
#[must_use]
pub fn trim_matches(&self, p: impl DoubleEndedPattern) -> Self {
let s = p.trim_matches(self.as_str());
unsafe { self.slice_ref_unchecked(s) }
}
#[inline]
#[must_use]
pub fn trim_start_matches(&self, p: impl Pattern) -> Self {
let s = p.trim_start_matches(self.as_str());
unsafe { self.slice_ref_unchecked(s) }
}
#[inline]
#[must_use]
pub fn trim_end_matches(&self, p: impl ReversePattern) -> Self {
let s = p.trim_end_matches(self.as_str());
unsafe { self.slice_ref_unchecked(s) }
}
#[inline]
pub fn strip_prefix(&self, prefix: impl Pattern) -> Option<Self> {
prefix
.strip_prefix(self.as_str())
.map(|s| unsafe { self.slice_ref_unchecked(s) })
}
#[inline]
pub fn strip_suffix(&self, suffix: impl ReversePattern) -> Option<Self> {
suffix
.strip_suffix(self.as_str())
.map(|s| unsafe { self.slice_ref_unchecked(s) })
}
#[inline]
pub fn split_whitespace(&self) -> IterWrapper<'_, 'borrow, B, SplitWhitespace<'_>> {
IterWrapper::new(self, self.as_str().split_whitespace())
}
#[inline]
pub fn split_ascii_whitespace(&self) -> IterWrapper<'_, 'borrow, B, SplitAsciiWhitespace<'_>> {
IterWrapper::new(self, self.as_str().split_ascii_whitespace())
}
#[inline]
pub fn lines(&self) -> IterWrapper<'_, 'borrow, B, Lines> {
IterWrapper::new(self, self.as_str().lines())
}
#[inline]
#[must_use]
pub fn concat_slices(slices: &[&str]) -> Self {
#[expect(clippy::transmute_ptr_to_ptr)]
let slices: &[&[u8]] = unsafe { transmute(slices) };
Self(HipByt::concat_slices(slices))
}
pub fn concat<E, I>(slices: I) -> Self
where
E: AsRef<str>,
I: IntoIterator<Item = E>,
I::IntoIter: Clone,
{
Self(HipByt::concat(slices.into_iter().map(AsBytes)))
}
#[inline]
#[must_use]
pub fn join_slices(slices: &[&str], sep: &str) -> Self {
#[expect(clippy::transmute_ptr_to_ptr)]
let slices: &[&[u8]] = unsafe { transmute(slices) };
Self(HipByt::join_slices(slices, sep.as_bytes()))
}
pub fn join<E, I>(slices: I, sep: impl AsRef<str>) -> Self
where
E: AsRef<str>,
I: IntoIterator<Item = E>,
I::IntoIter: Clone,
{
let iter = slices.into_iter().map(AsBytes);
Self(HipByt::join(iter, sep.as_ref().as_bytes()))
}
}
impl<B> HipStr<'static, B>
where
B: Backend,
{
#[inline]
#[must_use]
pub const fn from_static(value: &'static str) -> Self {
Self::borrowed(value)
}
}
impl<B> Clone for HipStr<'_, B>
where
B: Backend,
{
#[inline]
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<B> Default for HipStr<'_, B>
where
B: Backend,
{
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<B> Deref for HipStr<'_, B>
where
B: Backend,
{
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl<B> Borrow<str> for HipStr<'_, B>
where
B: Backend,
{
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<B> Hash for HipStr<'_, B>
where
B: Backend,
{
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<B> fmt::Debug for HipStr<'_, B>
where
B: Backend,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl<B> fmt::Display for HipStr<'_, B>
where
B: Backend,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SliceErrorKind {
StartGreaterThanEnd,
StartOutOfBounds,
EndOutOfBounds,
StartNotACharBoundary,
EndNotACharBoundary,
}
pub struct SliceError<'a, 'borrow, B>
where
B: Backend,
{
kind: SliceErrorKind,
start: usize,
end: usize,
string: &'a HipStr<'borrow, B>,
}
impl<B> Eq for SliceError<'_, '_, B> where B: Backend {}
impl<B> PartialEq for SliceError<'_, '_, B>
where
B: Backend,
{
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
&& self.start == other.start
&& self.end == other.end
&& self.string == other.string
}
}
impl<B> Clone for SliceError<'_, '_, B>
where
B: Backend,
{
fn clone(&self) -> Self {
*self
}
}
impl<B> Copy for SliceError<'_, '_, B> where B: Backend {}
impl<'a, 'borrow, B> SliceError<'a, 'borrow, B>
where
B: Backend,
{
const fn new(
kind: ByteSliceErrorKind,
start: usize,
end: usize,
string: &'a HipStr<'borrow, B>,
) -> Self {
let kind = match kind {
ByteSliceErrorKind::StartGreaterThanEnd => SliceErrorKind::StartGreaterThanEnd,
ByteSliceErrorKind::StartOutOfBounds => SliceErrorKind::StartOutOfBounds,
ByteSliceErrorKind::EndOutOfBounds => SliceErrorKind::EndOutOfBounds,
};
Self {
kind,
start,
end,
string,
}
}
#[inline]
#[must_use]
pub const fn kind(&self) -> SliceErrorKind {
self.kind
}
#[inline]
#[must_use]
pub const fn start(&self) -> usize {
self.start
}
#[inline]
#[must_use]
pub const fn end(&self) -> usize {
self.end
}
#[inline]
#[must_use]
pub const fn range(&self) -> Range<usize> {
self.start..self.end
}
#[inline]
#[must_use]
pub const fn source(&self) -> &HipStr<'borrow, B> {
self.string
}
}
impl<B> fmt::Debug for SliceError<'_, '_, B>
where
B: Backend,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SliceError")
.field("kind", &self.kind)
.field("start", &self.start)
.field("end", &self.end)
.field("string", &self.string)
.finish()
}
}
impl<B> fmt::Display for SliceError<'_, '_, B>
where
B: Backend,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
SliceErrorKind::StartGreaterThanEnd => write!(
f,
"range starts at {} but ends at {} when slicing `{}`",
self.start, self.end, self.string
),
SliceErrorKind::StartOutOfBounds => write!(
f,
"range start index {} is out of bounds of `{}`",
self.start, self.string
),
SliceErrorKind::EndOutOfBounds => write!(
f,
"range end index {} is out of bounds of `{}`",
self.end, self.string
),
SliceErrorKind::StartNotACharBoundary => write!(
f,
"range start index {} is not a char boundary of `{}`",
self.start, self.string
),
SliceErrorKind::EndNotACharBoundary => write!(
f,
"range end index {} is not a char boundary of `{}`",
self.end, self.string
),
}
}
}
impl<B> Error for SliceError<'_, '_, B> where B: Backend {}
pub struct FromUtf8Error<'borrow, B>
where
B: Backend,
{
pub(super) bytes: HipByt<'borrow, B>,
pub(super) error: Utf8Error,
}
impl<B> Eq for FromUtf8Error<'_, B> where B: Backend {}
impl<B> PartialEq for FromUtf8Error<'_, B>
where
B: Backend,
{
fn eq(&self, other: &Self) -> bool {
self.bytes == other.bytes && self.error == other.error
}
}
impl<B> Clone for FromUtf8Error<'_, B>
where
B: Backend,
{
fn clone(&self) -> Self {
Self {
bytes: self.bytes.clone(),
error: self.error,
}
}
}
impl<B> fmt::Debug for FromUtf8Error<'_, B>
where
B: Backend,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FromUtf8Error")
.field("bytes", &self.bytes)
.field("error", &self.error)
.finish()
}
}
impl<'borrow, B> FromUtf8Error<'borrow, B>
where
B: Backend,
{
#[must_use]
pub const fn as_bytes(&self) -> &[u8] {
self.bytes.as_slice()
}
#[must_use]
pub fn into_bytes(self) -> HipByt<'borrow, B> {
self.bytes
}
#[must_use]
pub const fn utf8_error(&self) -> Utf8Error {
self.error
}
}
impl<B> fmt::Display for FromUtf8Error<'_, B>
where
B: Backend,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.error, f)
}
}
impl<B> Error for FromUtf8Error<'_, B> where B: Backend {}
pub struct RefMut<'a, 'borrow, B>
where
B: Backend,
{
result: &'a mut HipStr<'borrow, B>,
owned: String,
}
impl<B> Drop for RefMut<'_, '_, B>
where
B: Backend,
{
fn drop(&mut self) {
let owned = core::mem::take(&mut self.owned);
*self.result = HipStr::from(owned);
}
}
impl<B> Deref for RefMut<'_, '_, B>
where
B: Backend,
{
type Target = String;
fn deref(&self) -> &Self::Target {
&self.owned
}
}
impl<B> DerefMut for RefMut<'_, '_, B>
where
B: Backend,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.owned
}
}
#[derive(Clone, Copy)]
struct AsBytes<T>(T);
impl<T: AsRef<str>> AsRef<[u8]> for AsBytes<T> {
#[inline]
fn as_ref(&self) -> &[u8] {
self.0.as_ref().as_bytes()
}
}