use std::fmt;
use std::iter::FusedIterator;
use crate::PascalString;
enum IntoCharsInner<const N: usize> {
Stack(PascalString<N>),
Heap(String),
}
impl<const N: usize> IntoCharsInner<N> {
#[inline]
fn as_str(&self) -> &str {
match self {
Self::Stack(s) => s.as_str(),
Self::Heap(s) => s.as_str(),
}
}
}
impl<const N: usize> Clone for IntoCharsInner<N> {
#[inline]
fn clone(&self) -> Self {
match self {
Self::Stack(s) => Self::Stack(*s),
Self::Heap(s) => Self::Heap(s.clone()),
}
}
}
pub struct IntoChars<const N: usize> {
inner: IntoCharsInner<N>,
front: usize,
back: usize,
}
impl<const N: usize> IntoChars<N> {
#[inline]
pub(crate) fn new_stack(s: PascalString<N>) -> Self {
let back = s.len();
Self {
inner: IntoCharsInner::Stack(s),
front: 0,
back,
}
}
#[inline]
pub(crate) fn new_heap(s: String) -> Self {
let back = s.len();
Self {
inner: IntoCharsInner::Heap(s),
front: 0,
back,
}
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
&self.inner.as_str()[self.front..self.back]
}
#[inline]
#[must_use]
pub fn into_string(self) -> String {
self.as_str().to_owned()
}
}
impl<const N: usize> Clone for IntoChars<N> {
#[inline]
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
front: self.front,
back: self.back,
}
}
}
impl<const N: usize> fmt::Debug for IntoChars<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IntoChars")
.field("remaining", &self.as_str())
.finish()
}
}
impl<const N: usize> fmt::Display for IntoChars<N> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<const N: usize> Iterator for IntoChars<N> {
type Item = char;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.front >= self.back {
return None;
}
let s = self.as_str();
let ch = s.chars().next().expect("non-empty str has first char");
self.front += ch.len_utf8();
Some(ch)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl<const N: usize> DoubleEndedIterator for IntoChars<N> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.front >= self.back {
return None;
}
let bytes = self.inner.as_str().as_bytes();
let mut pos = self.back - 1;
while (bytes[pos] & 0xC0) == 0x80 {
pos -= 1;
}
let ch_str = &self.inner.as_str()[pos..self.back];
let ch = ch_str.chars().next().expect("non-empty char slice");
self.back = pos;
Some(ch)
}
}
impl<const N: usize> ExactSizeIterator for IntoChars<N> {
#[inline]
fn len(&self) -> usize {
self.as_str().chars().count()
}
}
impl<const N: usize> FusedIterator for IntoChars<N> {}
#[cfg(test)]
mod tests {
use super::*;
use crate::SmartString;
fn stack_iter(s: &str) -> IntoChars<30> {
SmartString::<30>::from(s).into_chars()
}
fn heap_iter(s: &str) -> IntoChars<30> {
SmartString::<30>::from(s).into_chars()
}
#[test]
fn test_stack_forward() {
let chars: Vec<char> = stack_iter("hello").collect();
assert_eq!(chars, vec!['h', 'e', 'l', 'l', 'o']);
}
#[test]
fn test_heap_forward() {
let s = "a".repeat(32);
let chars: Vec<char> = heap_iter(&s).collect();
assert_eq!(chars, vec!['a'; 32]);
}
#[test]
fn test_empty() {
let mut it = stack_iter("");
assert_eq!(it.next(), None);
assert_eq!(it.next(), None);
}
#[test]
fn test_multibyte_emoji() {
let chars: Vec<char> = stack_iter("🦀ab").collect();
assert_eq!(chars, vec!['🦀', 'a', 'b']);
}
#[test]
fn test_multibyte_cjk() {
let chars: Vec<char> = stack_iter("你好").collect();
assert_eq!(chars, vec!['你', '好']);
}
#[test]
fn test_double_ended_reversed() {
let chars: Vec<char> = stack_iter("abc").rev().collect();
assert_eq!(chars, vec!['c', 'b', 'a']);
}
#[test]
fn test_double_ended_multibyte_reversed() {
let chars: Vec<char> = stack_iter("🦀你好").rev().collect();
assert_eq!(chars, vec!['好', '你', '🦀']);
}
#[test]
fn test_mixed_forward_backward() {
let mut it = stack_iter("abcde");
assert_eq!(it.next(), Some('a'));
assert_eq!(it.next_back(), Some('e'));
assert_eq!(it.next(), Some('b'));
assert_eq!(it.next_back(), Some('d'));
assert_eq!(it.next(), Some('c'));
assert_eq!(it.next(), None);
assert_eq!(it.next_back(), None);
}
#[test]
fn test_exact_size_len() {
let mut it = stack_iter("abc");
assert_eq!(it.len(), 3);
it.next();
assert_eq!(it.len(), 2);
it.next();
assert_eq!(it.len(), 1);
it.next();
assert_eq!(it.len(), 0);
it.next();
assert_eq!(it.len(), 0);
}
#[test]
fn test_exact_size_multibyte() {
let mut it = stack_iter("🦀你好");
assert_eq!(it.len(), 3);
it.next();
assert_eq!(it.len(), 2);
}
#[test]
fn test_as_str_partial() {
let mut it = stack_iter("hello");
it.next(); assert_eq!(it.as_str(), "ello");
it.next_back(); assert_eq!(it.as_str(), "ell");
}
#[test]
fn test_into_string_partial() {
let mut it = stack_iter("hello");
it.next(); it.next_back(); assert_eq!(it.into_string(), "ell");
}
#[test]
fn test_clone_independent() {
let mut it = stack_iter("abc");
let mut clone = it.clone();
assert_eq!(it.next(), Some('a'));
assert_eq!(clone.next(), Some('a'));
assert_eq!(clone.next(), Some('b'));
assert_eq!(it.next(), Some('b'));
}
#[test]
fn test_fused_returns_none_after_exhaustion() {
let mut it = stack_iter("x");
assert_eq!(it.next(), Some('x'));
assert_eq!(it.next(), None);
assert_eq!(it.next(), None);
assert_eq!(it.next_back(), None);
assert_eq!(it.next_back(), None);
}
#[test]
fn test_size_hint() {
let it = stack_iter("abc");
assert_eq!(it.size_hint(), (3, Some(3)));
}
}