use crate::perform::Perform;
#[inline]
const fn utf8_lead(lead: u8) -> (usize, u32) {
match lead {
0x00..=0x7f => (1, lead as u32),
0xc2..=0xdf => (2, (lead & 0x1f) as u32),
0xe0..=0xef => (3, (lead & 0x0f) as u32),
0xf0..=0xf4 => (4, (lead & 0x07) as u32),
_ => (0, 0),
}
}
#[cfg(feature = "simd")]
#[inline(always)]
pub(crate) const fn decode_utf8_n<const N: usize>(b: &[u8]) -> u32 {
match N {
2 => ((b[0] as u32 & 0x1f) << 6) | (b[1] as u32 & 0x3f),
3 => ((b[0] as u32 & 0x0f) << 12) | ((b[1] as u32 & 0x3f) << 6) | (b[2] as u32 & 0x3f),
_ => {
((b[0] as u32 & 0x07) << 18)
| ((b[1] as u32 & 0x3f) << 12)
| ((b[2] as u32 & 0x3f) << 6)
| (b[3] as u32 & 0x3f)
}
}
}
#[inline]
const fn utf8_second_byte_valid(lead: u8, second: u8) -> bool {
match lead {
0xe0 => second >= 0xa0 && second <= 0xbf,
0xed => second >= 0x80 && second <= 0x9f,
0xf0 => second >= 0x90 && second <= 0xbf,
0xf4 => second >= 0x80 && second <= 0x8f,
_ => true,
}
}
pub(crate) fn trailing_partial(s: &[u8]) -> Option<(usize, u32, u8)> {
let len = s.len();
if len == 0 {
return None;
}
let last = s[len - 1];
if last < 0x80 {
return None; }
if (0xc2..=0xf4).contains(&last) {
let (expected, cp) = utf8_lead(last);
return Some((len - 1, cp, (expected - 1) as u8));
}
if !(0x80..0xc0).contains(&last) {
return None;
}
let mut back = 1;
while back < 3 && len > back {
let b = s[len - 1 - back];
if (0x80..0xc0).contains(&b) {
back += 1;
} else {
break;
}
}
if back >= len {
return None;
}
let lead_idx = len - 1 - back;
let lead = s[lead_idx];
let (expected, lead_cp) = utf8_lead(lead);
let have = back + 1;
if expected == 0 || have >= expected {
return None;
}
if have >= 2 && !utf8_second_byte_valid(lead, s[lead_idx + 1]) {
return None;
}
let mut cp = lead_cp;
for &b in &s[lead_idx + 1..] {
cp = (cp << 6) | (b & 0x3f) as u32;
}
Some((lead_idx, cp, (expected - have) as u8))
}
#[inline(always)]
pub(crate) fn decode_utf8<P: Perform>(
p: &mut P,
bytes: &[u8],
valid: usize,
pos: usize,
simd: bool,
) -> usize {
#[cfg(feature = "simd")]
if simd {
const PROBE: usize = 32;
let probe_end = valid.min(pos + PROBE);
let short = bytes[pos..probe_end].contains(&0x1b) || valid - pos < PROBE;
if !short {
const CHUNK: usize = 4096;
let mut end = (pos + CHUNK).min(valid);
let rel = crate::simd::first_esc(&bytes[pos..end]);
if rel < end - pos {
end = pos + rel;
}
if end < valid {
let start = end;
while end > pos && (bytes[end] & 0xC0) == 0x80 {
end -= 1;
}
if end == pos {
end = start;
}
}
if let Ok(s) = simdutf8::basic::from_utf8(&bytes[pos..end]) {
let s = s.as_bytes();
#[cfg(feature = "simd-transcode")]
{
const BATCH: usize = 76;
let mut buf = [0_u32; BATCH];
let mut i = 0;
while i < s.len() {
let (count, consumed) =
crate::simd::decode_block_dispatch(&s[i..], &mut buf);
if !crate::simd::any_control(&buf[..count]) {
p.print_codepoints(&buf[..count]);
} else {
let mut start = 0;
for k in 0..count {
let cp = buf[k];
if cp < 0x20 || cp == 0x7f || (0x80..=0x9f).contains(&cp) {
if start < k {
p.print_codepoints(&buf[start..k]);
}
p.execute(cp as u8);
start = k + 1;
}
}
if start < count {
p.print_codepoints(&buf[start..count]);
}
}
i += consumed;
}
return end - pos;
}
#[cfg(not(feature = "simd-transcode"))]
{
let mut i = 0;
while i < s.len() {
let b = s[i];
match b {
0x1b => return i,
0x00..=0x1f => {
p.execute(b);
i += 1;
}
0x7f => {
p.execute(0x7f);
i += 1;
}
0x20..=0x7e => {
let n = crate::simd::first_non_printable(&s[i..]);
p.print_bytes(&s[i..i + n]);
i += n;
}
_ => {
const BATCH: usize = 32;
let mut buf = [0_u32; BATCH];
let mut n = 0;
loop {
let b0 = s[i];
if (0xe0..=0xef).contains(&b0) {
if i + 16 <= s.len()
&& (0xe0..=0xef).contains(&s[i + 3])
&& (0xe0..=0xef).contains(&s[i + 6])
&& (0xe0..=0xef).contains(&s[i + 9])
&& n + 4 <= BATCH
{
let cps =
crate::simd::decode_3byte_dispatch(&s[i..i + 16]);
buf[n..n + 4].copy_from_slice(&cps);
n += 4;
i += 12;
} else {
buf[n] = decode_utf8_n::<3>(&s[i..]);
n += 1;
if n == BATCH {
p.print_codepoints(&buf[..n]);
n = 0;
}
i += 3;
}
} else {
let (cp, len) = match b0 {
0xf0..=0xf4 => (decode_utf8_n::<4>(&s[i..]), 4),
_ => (decode_utf8_n::<2>(&s[i..]), 2),
};
if cp <= 0x9f {
if n > 0 {
p.print_codepoints(&buf[..n]);
n = 0;
}
p.execute(cp as u8);
} else {
buf[n] = cp;
n += 1;
if n == BATCH {
p.print_codepoints(&buf[..n]);
n = 0;
}
}
i += len;
}
if i >= s.len() || s[i] < 0x80 {
break;
}
}
if n > 0 {
p.print_codepoints(&buf[..n]);
}
}
}
}
return end - pos;
}
}
}
}
#[cfg(not(feature = "simd"))]
let _ = simd;
let mut it = utf8_iter::Utf8Chars::new(&bytes[pos..valid]);
let Some(c) = it.next() else {
return 1;
};
let consumed = (valid - pos) - it.as_slice().len();
if matches!(c, '\u{80}'..='\u{9f}') {
p.execute(c as u8);
} else {
p.print(c);
}
consumed.max(1)
}