use core::{fmt, iter::FusedIterator, slice};
use crate::{representation::Utf8FirstByte, Utf8Char, Utf8CharInner, TAG_CONTINUATION};
fn is_continuation(b: u8) -> bool {
const TAG_MASK: u8 = 0b1100_0000;
(b & TAG_MASK) == TAG_CONTINUATION
}
#[derive(Clone)]
pub struct Utf8CharIter<'slice> {
inner: slice::Iter<'slice, u8>,
}
impl fmt::Debug for Utf8CharIter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Utf8CharIter(")?;
f.debug_list().entries(self.clone()).finish()?;
write!(f, ")")
}
}
impl<'slice> Utf8CharIter<'slice> {
unsafe fn next_byte_unchecked(&mut self) -> u8 {
*unsafe { self.inner.next().unwrap_unchecked() }
}
unsafe fn next_byte_back_unchecked(&mut self) -> u8 {
*unsafe { self.inner.next_back().unwrap_unchecked() }
}
#[must_use]
pub fn new(s: &'slice str) -> Self {
Self {
inner: s.as_bytes().iter(),
}
}
unsafe fn next_unchecked(&mut self) -> Utf8Char {
let first = unsafe { self.next_byte_unchecked() };
let len = unsafe { Utf8FirstByte::new(first).codepoint_len() } as u8;
let mut ch = const { Utf8Char::from_char('0') };
let mut arr = *ch.0.as_array();
arr[0] = first;
if len > 1 {
arr[1] = unsafe { self.next_byte_unchecked() };
}
if len > 2 {
arr[2] = unsafe { self.next_byte_unchecked() };
}
if len > 3 {
arr[3] = unsafe { self.next_byte_unchecked() };
}
unsafe {
*ch.0.total_repr_mut() = arr;
}
ch
}
unsafe fn next_back_unchecked(&mut self) -> Utf8Char {
let mut arr = [0; 4];
let mut len = 1;
while len <= 4 {
let idx = unsafe { 4usize.unchecked_sub(len) };
arr[idx] = unsafe { self.next_byte_back_unchecked() };
if !is_continuation(arr[idx]) {
break;
}
len += 1;
}
let mut bits = u32::from_be_bytes(arr);
bits <<= unsafe { 4usize.unchecked_sub(len).unchecked_mul(8) };
bits |=
const { u32::from_be_bytes([0, TAG_CONTINUATION, TAG_CONTINUATION, TAG_CONTINUATION]) };
Utf8Char(unsafe { Utf8CharInner::from_utf8char_array(bits.to_be_bytes()) })
}
#[must_use]
fn is_empty(&self) -> bool {
self.inner.as_slice().is_empty()
}
#[must_use]
pub fn as_str(&self) -> &'slice str {
let slice = self.inner.as_slice();
unsafe { core::str::from_utf8_unchecked(slice) }
}
}
impl Iterator for Utf8CharIter<'_> {
type Item = Utf8Char;
fn next(&mut self) -> Option<Self::Item> {
if self.is_empty() {
None
} else {
Some(unsafe { self.next_unchecked() })
}
}
fn count(self) -> usize
where
Self: Sized,
{
self.as_str().chars().count()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.as_str().chars().size_hint()
}
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
}
impl DoubleEndedIterator for Utf8CharIter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.is_empty() {
None
} else {
Some(unsafe { self.next_back_unchecked() })
}
}
}
impl FusedIterator for Utf8CharIter<'_> {}
pub trait IntoUtf8Chars {
fn utf8_chars(&self) -> Utf8CharIter;
}
impl IntoUtf8Chars for str {
fn utf8_chars(&self) -> Utf8CharIter {
Utf8CharIter::new(self)
}
}
#[test]
fn allstring() {
use itertools::Itertools;
#[cfg(not(miri))]
let allchars = (char::MIN..=char::MAX).collect::<alloc::string::String>();
#[cfg(miri)]
let allchars = (char::MIN..=char::MAX)
.take(10_000)
.collect::<alloc::string::String>();
let utf8chars = Utf8CharIter::new(&allchars);
let chars = allchars.chars();
assert_eq!(utf8chars.clone().count(), chars.clone().count());
utf8chars
.clone()
.zip_eq(chars.clone())
.for_each(|(u8c, c)| {
assert_eq!(u8c.to_char(), c);
assert_eq!(Utf8Char::from_char(u8c.to_char()), u8c);
});
utf8chars.rev().zip_eq(chars.rev()).for_each(|(u8c, c)| {
assert_eq!(u8c.to_char(), c);
assert_eq!(Utf8Char::from_char(u8c.to_char()), u8c);
});
}