#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::should_implement_trait)]
#![deny(missing_docs)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::fmt::{Debug, Display};
use core::ops::{Bound, RangeBounds};
use core::str::FromStr;
#[cfg(feature = "std")]
use std::string::{String, ToString};
#[cfg(feature = "std")]
use std::vec::Vec;
pub trait IndexedStr:
Display + Debug + PartialEq<IndexedString> + for<'a> PartialEq<IndexedSlice<'a>>
{
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()
}
fn starts_with<S: AsRef<str>>(&self, s: S) -> bool {
self.as_str().starts_with(s.as_ref())
}
fn ends_with<S: AsRef<str>>(&self, s: S) -> bool {
self.as_str().ends_with(s.as_ref())
}
fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>
where
F: FromStr,
{
self.as_str().parse()
}
fn lines(&self) -> IndexedLines<'_>;
}
#[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(),
}
}
fn lines(&self) -> IndexedLines<'_> {
IndexedLines {
source: self,
start: 0,
}
}
}
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.chars().collect(),
offsets: s.char_indices().map(|(i, _)| i).collect(),
string: s,
}
}
pub fn from_chars(chars: impl Iterator<Item = char>) -> Self {
let chars: Vec<char> = chars.collect();
let offsets: Vec<usize> = chars
.iter()
.scan(0, |acc, &c| {
let offset = *acc;
*acc += c.len_utf8();
Some(offset)
})
.collect();
let string: String = chars.iter().collect();
IndexedString {
chars,
offsets,
string,
}
}
}
impl AsRef<str> for IndexedString {
fn as_ref(&self) -> &str {
&self.string
}
}
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 IndexedStr for IndexedSlice<'_> {
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().iter().copied())
}
fn as_slice(&self) -> IndexedSlice<'_> {
self.clone()
}
fn lines(&self) -> IndexedLines<'_> {
IndexedLines {
source: self.source,
start: self.start,
}
}
}
impl<S: AsRef<str>> PartialEq<S> for IndexedSlice<'_> {
fn eq(&self, other: &S) -> bool {
self.as_str() == other.as_ref()
}
}
impl AsRef<str> for IndexedSlice<'_> {
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 Display for IndexedSlice<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl IndexedStr for &IndexedString {
fn as_str(&self) -> &str {
(*self).as_str()
}
fn as_slice(&self) -> IndexedSlice<'_> {
(*self).as_slice()
}
fn len(&self) -> usize {
(*self).len()
}
fn byte_len(&self) -> usize {
(*self).byte_len()
}
fn char_at(&self, index: usize) -> Option<char> {
(*self).char_at(index)
}
fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice<'_> {
(*self).slice(range)
}
fn chars(&self) -> &[char] {
(*self).chars()
}
fn to_indexed_string(&self) -> IndexedString {
(*self).to_indexed_string()
}
fn lines(&self) -> IndexedLines<'_> {
(*self).lines()
}
}
impl PartialEq<IndexedString> for &IndexedString {
fn eq(&self, other: &IndexedString) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<IndexedSlice<'_>> for &IndexedString {
fn eq(&self, other: &IndexedSlice) -> bool {
self.as_str() == other.as_str()
}
}
impl IndexedStr for &IndexedSlice<'_> {
fn as_str(&self) -> &str {
(*self).as_str()
}
fn as_slice(&self) -> IndexedSlice<'_> {
(*self).as_slice()
}
fn len(&self) -> usize {
(*self).len()
}
fn byte_len(&self) -> usize {
(*self).byte_len()
}
fn char_at(&self, index: usize) -> Option<char> {
(*self).char_at(index)
}
fn slice<R: RangeBounds<usize>>(&self, range: R) -> IndexedSlice<'_> {
(*self).slice(range)
}
fn chars(&self) -> &[char] {
(*self).chars()
}
fn to_indexed_string(&self) -> IndexedString {
(*self).to_indexed_string()
}
fn lines(&self) -> IndexedLines<'_> {
(*self).lines()
}
}
impl PartialEq<IndexedString> for &IndexedSlice<'_> {
fn eq(&self, other: &IndexedString) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<IndexedSlice<'_>> for &IndexedSlice<'_> {
fn eq(&self, other: &IndexedSlice) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<IndexedSlice<'_>> for &str {
fn eq(&self, other: &IndexedSlice) -> bool {
other.as_str() == *self
}
}
impl PartialEq<IndexedSlice<'_>> for String {
fn eq(&self, other: &IndexedSlice) -> bool {
other.as_str() == *self
}
}
impl PartialEq<IndexedString> for &str {
fn eq(&self, other: &IndexedString) -> bool {
other.as_str() == *self
}
}
impl PartialEq<IndexedString> for String {
fn eq(&self, other: &IndexedString) -> bool {
other.as_str() == *self
}
}
pub struct IndexedLines<'a> {
source: &'a IndexedString,
start: usize,
}
impl<'a> Iterator for IndexedLines<'a> {
type Item = IndexedSlice<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.start > self.source.chars.len() {
return None;
}
if self.start == self.source.chars.len() {
self.start += 1; return Some(self.source.slice(self.start - 1..self.start - 1));
}
let mut end = self.start;
while end < self.source.chars.len() {
if self.source.chars[end] == '\n' {
let line = self.source.slice(self.start..end);
self.start = end + 1; return Some(line);
}
end += 1;
}
if self.start <= self.source.chars.len() {
let line = self.source.slice(self.start..self.source.chars.len());
self.start = self.source.chars.len() + 1; return Some(line);
}
None
}
}