use crate::{
iter::{ConstIntoIter, IsIteratorKind},
string,
};
use crate::string::{__find_next_char_boundary, __find_prev_char_boundary};
pub(super) const fn string_to_usv(s: &str) -> u32 {
match *s.as_bytes() {
[a] => a as _,
[a, b] => ((a as u32 & 0x1F) << 6) | (b as u32 & 0x7F),
[a, b, c] => ((a as u32 & 0xF) << 12) | ((b as u32 & 0x3F) << 6) | (c as u32 & 0x3F),
[a, b, c, d] => {
((a as u32 & 0x7) << 18)
| ((b as u32 & 0x3F) << 12)
| ((c as u32 & 0x3F) << 6)
| (d as u32 & 0x3F)
}
_ => {
#[cfg(feature = "debug")]
{
panic!("string must be a single char long")
}
#[cfg(not(feature = "debug"))]
{
0
}
}
}
}
pub(super) const fn string_to_char(s: &str) -> char {
let c: u32 = string_to_usv(s);
unsafe { char::from_u32_unchecked(c) }
}
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "iter")))]
pub const fn chars(string: &str) -> Chars<'_> {
Chars { this: string }
}
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "iter")))]
pub struct Chars<'a> {
this: &'a str,
}
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "iter")))]
pub struct RChars<'a> {
this: &'a str,
}
impl ConstIntoIter for Chars<'_> {
type Kind = IsIteratorKind;
type IntoIter = Self;
type Item = char;
const ITEMS_NEED_DROP: bool = false;
}
impl ConstIntoIter for RChars<'_> {
type Kind = IsIteratorKind;
type IntoIter = Self;
type Item = char;
const ITEMS_NEED_DROP: bool = false;
}
macro_rules! chars_shared {
(is_forward = $is_forward:ident) => {
iterator_shared! {
is_forward = $is_forward,
item = char,
iter_forward = Chars<'a>,
iter_reversed = RChars<'a>,
next(self){
if self.this.is_empty() {
return None
}
let split_at = __find_next_char_boundary(self.this.as_bytes(), 0);
let (prev, next) = string::split_at(self.this, split_at);
self.this = next;
Some(string_to_char(prev))
},
next_back{
if self.this.is_empty() {
return None
}
let split_at = __find_prev_char_boundary(self.this.as_bytes(), self.this.len());
let (prev, next) = string::split_at(self.this, split_at);
self.this = prev;
Some(string_to_char(next))
},
fields = {this},
}
};
}
impl<'a> Chars<'a> {
chars_shared! {is_forward = true}
pub const fn as_str(&self) -> &'a str {
self.this
}
}
impl<'a> RChars<'a> {
chars_shared! {is_forward = false}
}
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "iter")))]
pub const fn char_indices(string: &str) -> CharIndices<'_> {
CharIndices {
this: string,
start_offset: 0,
}
}
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "iter")))]
pub struct CharIndices<'a> {
this: &'a str,
start_offset: usize,
}
#[cfg_attr(feature = "docsrs", doc(cfg(feature = "iter")))]
pub struct RCharIndices<'a> {
this: &'a str,
start_offset: usize,
}
impl ConstIntoIter for CharIndices<'_> {
type Kind = IsIteratorKind;
type IntoIter = Self;
type Item = (usize, char);
const ITEMS_NEED_DROP: bool = false;
}
impl ConstIntoIter for RCharIndices<'_> {
type Kind = IsIteratorKind;
type IntoIter = Self;
type Item = (usize, char);
const ITEMS_NEED_DROP: bool = false;
}
macro_rules! chars_shared {
(is_forward = $is_forward:ident) => {
iterator_shared! {
is_forward = $is_forward,
item = (usize, char),
iter_forward = CharIndices<'a>,
iter_reversed = RCharIndices<'a>,
next(self){
if self.this.is_empty() {
return None
}
let split_at = __find_next_char_boundary(self.this.as_bytes(), 0);
let (prev, next) = string::split_at(self.this, split_at);
let ret_i = self.start_offset;
*self = Self {
this: next,
start_offset: self.start_offset + split_at,
};
Some((ret_i, string_to_char(prev)))
},
next_back{
if self.this.is_empty() {
return None
}
let split_at = __find_prev_char_boundary(self.this.as_bytes(), self.this.len());
let (prev, next) = string::split_at(self.this, split_at);
let ret_i = self.start_offset + split_at;
*self = Self {
this: prev,
start_offset: self.start_offset,
};
Some((ret_i, string_to_char(next)))
},
fields = {this, start_offset},
}
};
}
impl<'a> CharIndices<'a> {
chars_shared! {is_forward = true}
pub const fn as_str(&self) -> &'a str {
self.this
}
}
impl<'a> RCharIndices<'a> {
chars_shared! {is_forward = false}
}