#![deny(missing_docs)]
use core::fmt::{Debug, Display};
use core::ops::{Bound, Index, RangeBounds};
pub trait IndexedStr:
Display + Debug + PartialEq<IndexedString> + for<'a> PartialEq<IndexedSlice<'a>> + Index<usize>
{
fn as_str(&self) -> &str;
fn as_slice(&self) -> IndexedSlice;
fn len(&self) -> usize;
fn byte_len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn char_at(&self, index: usize) -> Option<char>;
fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice;
fn chars(&self) -> &[char];
fn to_indexed_string(&self) -> IndexedString;
fn to_lowercase(&self) -> IndexedString {
self.as_str().to_lowercase().into()
}
fn to_uppercase(&self) -> IndexedString {
self.as_str().to_uppercase().into()
}
}
#[derive(Clone, Debug, Eq, Hash)]
pub struct IndexedString {
chars: Vec<char>,
offsets: Vec<usize>,
string: String,
}
impl IndexedStr for IndexedString {
fn as_str(&self) -> &str {
&self.string
}
fn char_at(&self, index: usize) -> Option<char> {
self.chars.get(index).copied()
}
fn chars(&self) -> &[char] {
&self.chars[..]
}
fn len(&self) -> usize {
self.chars.len()
}
fn byte_len(&self) -> usize {
self.string.len()
}
fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice {
let start = match range.start_bound() {
Bound::Included(&start) => start,
Bound::Excluded(&start) => start + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&end) => end + 1,
Bound::Excluded(&end) => end,
Bound::Unbounded => self.chars.len(),
};
let start = if start > self.chars.len() {
self.chars.len()
} else {
start
};
let end = if end > self.chars.len() {
self.chars.len()
} else {
end
};
IndexedSlice {
source: self,
start,
end,
}
}
fn to_indexed_string(&self) -> IndexedString {
self.clone()
}
fn as_slice(&self) -> IndexedSlice {
IndexedSlice {
source: self,
start: 0,
end: self.chars.len(),
}
}
}
impl IndexedString {
pub fn from_str(s: impl Display) -> Self {
IndexedString::from_string(s.to_string())
}
pub fn from_string(s: String) -> Self {
IndexedString {
chars: s.to_string().chars().collect(),
offsets: s.to_string().char_indices().map(|(i, _)| i).collect(),
string: s.to_string(),
}
}
pub fn from_chars(chars: impl Iterator<Item = char>) -> Self {
let chars: Vec<char> = chars.collect();
let offsets: Vec<usize> = chars.iter().enumerate().map(|(i, _)| i).collect();
let string: String = chars.iter().collect();
IndexedString {
chars,
offsets,
string,
}
}
}
impl AsRef<str> for IndexedString {
fn as_ref(&self) -> &str {
&self.string
}
}
impl Index<usize> for IndexedString {
type Output = char;
fn index(&self, index: usize) -> &Self::Output {
&self.chars[index]
}
}
impl Display for IndexedString {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.string)
}
}
impl<S: AsRef<str>> PartialEq<S> for IndexedString {
fn eq(&self, other: &S) -> bool {
self.string == other.as_ref()
}
}
#[derive(Eq, Debug, Clone)]
pub struct IndexedSlice<'a> {
source: &'a IndexedString,
start: usize,
end: usize,
}
impl<'a> IndexedStr for IndexedSlice<'a> {
fn as_str(&self) -> &str {
if self.start >= self.source.offsets.len()
|| self.end > self.source.offsets.len()
|| self.start > self.end
{
return "";
}
let start_byte = self.source.offsets[self.start];
let end_byte = if self.end == self.source.offsets.len() {
self.source.string.len()
} else {
self.source.offsets[self.end]
};
&self.source.string[start_byte..end_byte]
}
fn len(&self) -> usize {
self.end - self.start
}
fn byte_len(&self) -> usize {
self.source.offsets[self.end] - self.source.offsets[self.start]
}
fn char_at(&self, index: usize) -> Option<char> {
self.source.char_at(self.start + index)
}
fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice {
let start = match range.start_bound() {
Bound::Included(&start) => start,
Bound::Excluded(&start) => start + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&end) => end + 1,
Bound::Excluded(&end) => end,
Bound::Unbounded => self.len(),
};
let start = if start > self.len() {
self.len()
} else {
start
};
let end = if end > self.len() { self.len() } else { end };
IndexedSlice {
source: self.source,
start: self.start + start,
end: self.start + end,
}
}
fn chars(&self) -> &[char] {
&self.source.chars[self.start..self.end]
}
fn to_indexed_string(&self) -> IndexedString {
IndexedString::from_chars(self.chars().into_iter().copied())
}
fn as_slice(&self) -> IndexedSlice {
self.clone()
}
}
impl<'a, S: AsRef<str>> PartialEq<S> for IndexedSlice<'a> {
fn eq(&self, other: &S) -> bool {
self.as_str() == other.as_ref()
}
}
impl<'a> AsRef<str> for IndexedSlice<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> From<&'a IndexedString> for IndexedSlice<'a> {
fn from(s: &'a IndexedString) -> Self {
IndexedSlice {
source: s,
start: 0,
end: s.chars.len(),
}
}
}
impl<'a> From<IndexedSlice<'a>> for IndexedString {
fn from(s: IndexedSlice<'a>) -> Self {
s.to_indexed_string()
}
}
impl From<String> for IndexedString {
fn from(s: String) -> Self {
IndexedString::from_string(s)
}
}
impl From<&str> for IndexedString {
fn from(s: &str) -> Self {
IndexedString::from_str(s)
}
}
impl From<&String> for IndexedString {
fn from(s: &String) -> Self {
IndexedString::from_string(s.clone())
}
}
impl<'a> Display for IndexedSlice<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl Index<usize> for IndexedSlice<'_> {
type Output = char;
fn index(&self, index: usize) -> &Self::Output {
&self.source.chars[self.start + index]
}
}