#![allow(unsafe_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsciiFoldMode {
Upper,
Lower,
Title,
}
#[derive(Debug, Clone, Copy)]
pub struct AsciiFastResult {
pub consumed: usize,
pub at_word_start: bool,
}
#[inline]
pub fn scan_ascii_prefix(bytes: &[u8]) -> usize {
scan_ascii_prefix_impl(bytes)
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[inline]
fn scan_ascii_prefix_impl(bytes: &[u8]) -> usize {
use core::arch::wasm32::*;
let len = bytes.len();
let mut i = 0usize;
while i + 16 <= len {
let chunk = unsafe { v128_load(bytes.as_ptr().add(i) as *const v128) };
let mask = i8x16_bitmask(chunk);
if mask != 0 {
return i + mask.trailing_zeros() as usize;
}
i += 16;
}
while i < len {
if bytes[i] >= 0x80 {
return i;
}
i += 1;
}
len
}
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
#[inline]
fn scan_ascii_prefix_impl(bytes: &[u8]) -> usize {
let len = bytes.len();
let mut i = 0usize;
const HIGH_BITS: u64 = 0x8080_8080_8080_8080;
while i + 8 <= len {
let mut buf = [0u8; 8];
buf.copy_from_slice(&bytes[i..i + 8]);
let chunk = u64::from_le_bytes(buf);
let hits = chunk & HIGH_BITS;
if hits != 0 {
return i + (hits.trailing_zeros() as usize) / 8;
}
i += 8;
}
while i < len {
if bytes[i] >= 0x80 {
return i;
}
i += 1;
}
len
}
#[inline]
fn fold_ascii_prefix_upper_lower(prefix: &[u8], upper: bool, out: &mut Vec<u8>) {
out.reserve(prefix.len());
fold_ascii_upper_lower_impl(prefix, upper, out);
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[inline]
fn fold_ascii_upper_lower_impl(prefix: &[u8], upper: bool, out: &mut Vec<u8>) {
use core::arch::wasm32::*;
let len = prefix.len();
let start = out.len();
out.resize(start + len, 0);
let dst = &mut out[start..start + len];
let (lo, hi) = if upper {
(u8x16_splat(b'a'), u8x16_splat(b'z'))
} else {
(u8x16_splat(b'A'), u8x16_splat(b'Z'))
};
let bit = u8x16_splat(0x20);
let mut i = 0usize;
while i + 16 <= len {
let chunk = unsafe { v128_load(prefix.as_ptr().add(i) as *const v128) };
let ge_lo = u8x16_ge(chunk, lo);
let le_hi = u8x16_le(chunk, hi);
let in_range = v128_and(ge_lo, le_hi);
let xor_mask = v128_and(in_range, bit);
let folded = v128_xor(chunk, xor_mask);
unsafe {
v128_store(dst.as_mut_ptr().add(i) as *mut v128, folded);
}
i += 16;
}
while i < len {
let b = prefix[i];
dst[i] = if upper {
if b.is_ascii_lowercase() {
b ^ 0x20
} else {
b
}
} else if b.is_ascii_uppercase() {
b ^ 0x20
} else {
b
};
i += 1;
}
}
#[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
#[inline]
fn fold_ascii_upper_lower_impl(prefix: &[u8], upper: bool, out: &mut Vec<u8>) {
let len = prefix.len();
let start = out.len();
out.resize(start + len, 0);
let dst = &mut out[start..start + len];
if upper {
for i in 0..len {
let b = prefix[i];
let in_range = b.wrapping_sub(b'a') < 26;
let flip = if in_range { 0x20 } else { 0x00 };
dst[i] = b ^ flip;
}
} else {
for i in 0..len {
let b = prefix[i];
let in_range = b.wrapping_sub(b'A') < 26;
let flip = if in_range { 0x20 } else { 0x00 };
dst[i] = b ^ flip;
}
}
}
#[inline]
fn fold_ascii_prefix_title(prefix: &[u8], at_word_start_in: bool, out: &mut Vec<u8>) -> bool {
let len = prefix.len();
let start = out.len();
out.reserve(len);
out.resize(start + len, 0);
let dst = &mut out[start..start + len];
let mut at_word_start = at_word_start_in;
for i in 0..len {
let b = prefix[i];
let is_ws = matches!(b, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r');
if is_ws {
dst[i] = b;
at_word_start = true;
continue;
}
if at_word_start {
dst[i] = if b.is_ascii_lowercase() { b & 0xDF } else { b };
} else {
dst[i] = if b.is_ascii_uppercase() { b | 0x20 } else { b };
}
at_word_start = false;
}
at_word_start
}
#[inline]
pub fn fold_ascii_prefix(
bytes: &[u8],
mode: AsciiFoldMode,
at_word_start_in: bool,
out: &mut Vec<u8>,
) -> AsciiFastResult {
let prefix_len = scan_ascii_prefix(bytes);
if prefix_len == 0 {
return AsciiFastResult {
consumed: 0,
at_word_start: at_word_start_in,
};
}
let prefix = &bytes[..prefix_len];
let at_word_start = match mode {
AsciiFoldMode::Upper => {
fold_ascii_prefix_upper_lower(prefix, true, out);
at_word_start_in
}
AsciiFoldMode::Lower => {
fold_ascii_prefix_upper_lower(prefix, false, out);
at_word_start_in
}
AsciiFoldMode::Title => fold_ascii_prefix_title(prefix, at_word_start_in, out),
};
AsciiFastResult {
consumed: prefix_len,
at_word_start,
}
}
#[inline]
pub fn case_fold_ascii_fast(
bytes: &[u8],
mode: AsciiFoldMode,
at_word_start_in: bool,
out: &mut Vec<u8>,
) -> AsciiFastResult {
debug_assert!(
bytes.iter().all(|b| *b < 0x80),
"case_fold_ascii_fast called with non-ASCII payload — caller must check flag bit first"
);
if bytes.is_empty() {
return AsciiFastResult {
consumed: 0,
at_word_start: at_word_start_in,
};
}
let at_word_start = match mode {
AsciiFoldMode::Upper => {
fold_ascii_prefix_upper_lower(bytes, true, out);
at_word_start_in
}
AsciiFoldMode::Lower => {
fold_ascii_prefix_upper_lower(bytes, false, out);
at_word_start_in
}
AsciiFoldMode::Title => fold_ascii_prefix_title(bytes, at_word_start_in, out),
};
AsciiFastResult {
consumed: bytes.len(),
at_word_start,
}
}
#[inline]
pub fn case_fold_ascii_fast_into_string(
bytes: &[u8],
mode: AsciiFoldMode,
at_word_start_in: bool,
out: &mut String,
) -> AsciiFastResult {
let pre_len = out.len();
let result = {
let buf = unsafe { out.as_mut_vec() };
case_fold_ascii_fast(bytes, mode, at_word_start_in, buf)
};
debug_assert!(
out.is_char_boundary(pre_len),
"case_fold_ascii_fast corrupted utf-8 boundary at {pre_len}"
);
result
}
#[inline]
pub fn fold_ascii_prefix_into_string(
bytes: &[u8],
mode: AsciiFoldMode,
at_word_start_in: bool,
out: &mut String,
) -> AsciiFastResult {
let pre_len = out.len();
let result = {
let buf = unsafe { out.as_mut_vec() };
fold_ascii_prefix(bytes, mode, at_word_start_in, buf)
};
let new_bytes = &out.as_bytes()[pre_len..pre_len + result.consumed];
assert!(
new_bytes.iter().all(|b| *b < 0x80),
"ascii fold emitted non-ASCII byte; UTF-8 invariant would be broken"
);
debug_assert!(
out.is_char_boundary(pre_len),
"ascii fold corrupted utf-8 boundary at {pre_len}"
);
result
}
#[cfg(test)]
mod tests {
use super::*;
fn reference_fold(s: &[u8], mode: AsciiFoldMode) -> Vec<u8> {
let mut out = Vec::with_capacity(s.len());
let mut at_word_start = true;
for &b in s {
match mode {
AsciiFoldMode::Upper => {
out.push(if b.is_ascii_lowercase() { b & 0xDF } else { b });
}
AsciiFoldMode::Lower => {
out.push(if b.is_ascii_uppercase() { b | 0x20 } else { b });
}
AsciiFoldMode::Title => {
let is_ws = matches!(b, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r');
if is_ws {
out.push(b);
at_word_start = true;
} else {
if at_word_start {
out.push(if b.is_ascii_lowercase() { b & 0xDF } else { b });
} else {
out.push(if b.is_ascii_uppercase() { b | 0x20 } else { b });
}
at_word_start = false;
}
}
}
}
out
}
#[test]
fn scan_prefix_empty() {
assert_eq!(scan_ascii_prefix(b""), 0);
}
#[test]
fn scan_prefix_all_ascii() {
let s = b"Hello, world!";
assert_eq!(scan_ascii_prefix(s), s.len());
}
#[test]
fn scan_prefix_immediate_non_ascii() {
let s = "\u{00DF}foo".as_bytes();
assert_eq!(scan_ascii_prefix(s), 0);
}
#[test]
fn scan_prefix_mixed() {
let s = "abc\u{00DF}def".as_bytes();
assert_eq!(scan_ascii_prefix(s), 3);
}
#[test]
fn scan_prefix_15_then_non_ascii() {
let s = "abcdefghijklmno\u{00DF}".as_bytes();
assert_eq!(scan_ascii_prefix(s), 15);
}
#[test]
fn scan_prefix_exactly_16() {
let s = b"abcdefghijklmnop";
assert_eq!(scan_ascii_prefix(s), 16);
}
#[test]
fn scan_prefix_17() {
let s = b"abcdefghijklmnopq";
assert_eq!(scan_ascii_prefix(s), 17);
}
#[test]
fn scan_prefix_32_then_non_ascii() {
let s = "abcdefghijklmnopqrstuvwxyz123456\u{00DF}".as_bytes();
assert_eq!(scan_ascii_prefix(s), 32);
}
#[test]
fn scan_prefix_non_ascii_in_second_chunk() {
let s = "abcdefghijklmnopqrst\u{00DF}".as_bytes();
assert_eq!(scan_ascii_prefix(s), 20);
}
#[test]
fn upper_lower_byte_identical_small() {
for src in [
b"".as_ref(),
b"a",
b"A",
b"Hello, World!",
b"0123456789",
b"AAAaaa BBBbbb CCCccc",
b"\x00\x01\x7F",
] {
for mode in [AsciiFoldMode::Upper, AsciiFoldMode::Lower] {
let mut out = Vec::new();
let r = fold_ascii_prefix(src, mode, true, &mut out);
assert_eq!(r.consumed, src.len(), "consumed mismatch for {src:?}");
assert_eq!(out, reference_fold(src, mode), "{src:?} mode {mode:?}");
}
}
}
#[test]
fn title_byte_identical_small() {
for src in [
b"hello world".as_ref(),
b"HELLO WORLD",
b"the quick brown fox jumps over the lazy dog",
b" leading spaces",
b"\ttab\nnewline\rcr",
b"a",
b"",
] {
let mut out = Vec::new();
let r = fold_ascii_prefix(src, AsciiFoldMode::Title, true, &mut out);
assert_eq!(r.consumed, src.len());
assert_eq!(out, reference_fold(src, AsciiFoldMode::Title), "{src:?}");
}
}
#[test]
fn boundary_lengths_16_17_32_33() {
for &n in &[1usize, 15, 16, 17, 31, 32, 33, 47, 48, 64] {
let src: Vec<u8> = (0..n)
.map(|i| {
b"AaBbCc0 .Z!9zXyW"[i % 16]
})
.collect();
for mode in [
AsciiFoldMode::Upper,
AsciiFoldMode::Lower,
AsciiFoldMode::Title,
] {
let mut out = Vec::new();
let r = fold_ascii_prefix(&src, mode, true, &mut out);
assert_eq!(r.consumed, src.len());
assert_eq!(
out,
reference_fold(&src, mode),
"n={n} mode={mode:?} src={src:?}"
);
}
}
}
#[test]
fn pseudo_random_ascii_corpora() {
let mut state: u32 = 0x6d61_6e69;
let mut next = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for trial in 0..16 {
let len = 50 + (next() as usize % 1000);
let src: Vec<u8> = (0..len)
.map(|_| {
let r = next() % 96;
let b = 0x20 + r as u8;
if b == 0x7F {
b' '
} else {
b
}
})
.collect();
for mode in [
AsciiFoldMode::Upper,
AsciiFoldMode::Lower,
AsciiFoldMode::Title,
] {
let mut out = Vec::new();
let r = fold_ascii_prefix(&src, mode, true, &mut out);
assert_eq!(r.consumed, src.len(), "trial={trial} mode={mode:?}");
assert_eq!(
out,
reference_fold(&src, mode),
"trial={trial} mode={mode:?} len={len}"
);
}
}
}
#[test]
fn title_at_word_start_carry() {
let mut out = Vec::new();
let r = fold_ascii_prefix(b"WORLD", AsciiFoldMode::Title, false, &mut out);
assert_eq!(out, b"world");
assert!(!r.at_word_start);
}
#[test]
fn title_word_start_ends_on_whitespace() {
let mut out = Vec::new();
let r = fold_ascii_prefix(b"hello ", AsciiFoldMode::Title, true, &mut out);
assert_eq!(out, b"Hello ");
assert!(r.at_word_start);
}
#[test]
fn ascii_prefix_with_nonascii_tail_consumes_only_prefix() {
let s = "Hello, \u{00DF}world".as_bytes(); let mut out = Vec::new();
let r = fold_ascii_prefix(s, AsciiFoldMode::Upper, true, &mut out);
assert_eq!(r.consumed, 7);
assert_eq!(out, b"HELLO, ");
}
#[test]
fn ascii_prefix_consumed_zero_when_first_byte_nonascii() {
let s = "\u{00DF}foo".as_bytes();
let mut out = Vec::new();
let r = fold_ascii_prefix(s, AsciiFoldMode::Upper, true, &mut out);
assert_eq!(r.consumed, 0);
assert!(out.is_empty());
}
#[test]
fn case_fold_ascii_fast_matches_scan_path_byte_identical() {
for src in [
b"".as_ref(),
b"a",
b"A",
b"Hello, World!",
b"0123456789",
b"AAAaaa BBBbbb CCCccc",
b"\x00\x01\x7F",
b"the quick brown fox",
] {
for mode in [
AsciiFoldMode::Upper,
AsciiFoldMode::Lower,
AsciiFoldMode::Title,
] {
let mut via_scan = Vec::new();
let r_scan = fold_ascii_prefix(src, mode, true, &mut via_scan);
let mut via_fast = Vec::new();
let r_fast = case_fold_ascii_fast(src, mode, true, &mut via_fast);
assert_eq!(
via_scan, via_fast,
"src={src:?} mode={mode:?}: scan vs fast path divergence"
);
assert_eq!(r_scan.consumed, r_fast.consumed);
assert_eq!(r_scan.at_word_start, r_fast.at_word_start);
}
}
}
#[test]
fn case_fold_ascii_fast_into_string_appends_correctly() {
let mut acc = String::from("prefix:");
let r = case_fold_ascii_fast_into_string(b"Hello", AsciiFoldMode::Lower, true, &mut acc);
assert_eq!(acc, "prefix:hello");
assert_eq!(r.consumed, 5);
}
#[test]
fn case_fold_ascii_fast_empty_input_is_noop() {
let mut out = Vec::new();
let r = case_fold_ascii_fast(b"", AsciiFoldMode::Upper, true, &mut out);
assert_eq!(r.consumed, 0);
assert!(r.at_word_start);
assert!(out.is_empty());
}
#[test]
fn case_fold_ascii_fast_title_carries_word_state() {
let mut out = Vec::new();
let r = case_fold_ascii_fast(b"hello world", AsciiFoldMode::Title, true, &mut out);
assert_eq!(out, b"Hello World");
assert_eq!(r.consumed, 11);
assert!(!r.at_word_start);
}
}