#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Utf8Chunk {
Valid(char),
Invalid(u8),
}
pub struct Utf8Iter<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Utf8Iter<'a> {
pub fn new(bytes: &'a [u8]) -> Self {
Utf8Iter { bytes, pos: 0 }
}
}
impl<'a> Iterator for Utf8Iter<'a> {
type Item = Utf8Chunk;
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.bytes.len() {
return None;
}
let remaining = &self.bytes[self.pos..];
match std::str::from_utf8(remaining) {
Ok(valid) => {
let mut chars = valid.chars();
if let Some(c) = chars.next() {
self.pos += c.len_utf8();
Some(Utf8Chunk::Valid(c))
} else {
None
}
}
Err(err) => {
let valid_up_to = err.valid_up_to();
if valid_up_to > 0 {
let valid_str =
unsafe { std::str::from_utf8_unchecked(&remaining[..valid_up_to]) };
let c = valid_str.chars().next().unwrap();
self.pos += c.len_utf8();
Some(Utf8Chunk::Valid(c))
} else {
let b = self.bytes[self.pos];
self.pos += 1;
Some(Utf8Chunk::Invalid(b))
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_utf8_iter() {
let bytes = b"hello \xF0\x90world\xC3(";
for chunk in Utf8Iter::new(bytes) {
match chunk {
Utf8Chunk::Valid(c) => print!("{}", c),
Utf8Chunk::Invalid(b) => print!("�(0x{:02X})", b),
}
}
}
}