use crate::table::*;
use crate::{popcount_up_to, scan_end_low, utf8_len};
pub fn simple_fold(s: String) -> String {
unsafe { String::from_utf8_unchecked(fold_into_bytes(s)) }
}
pub fn simple_fold_char(c: char) -> char {
let cp = c as u32;
if cp < 0x80 {
let b = cp as u8;
let is_upper = b.wrapping_sub(b'A') < 26;
return char::from(b | (u8::from(is_upper) << 5));
}
let word_idx = (cp >> 12) as usize;
let bit_idx = (cp >> 6) & 0x3F;
let low_v = (cp & 0x3F) as u8;
if word_idx < PAGE_BITMAP.len() && (PAGE_BITMAP[word_idx] >> bit_idx) & 1 != 0 {
let dense = popcount_up_to(word_idx, bit_idx) as usize;
let lo = PAGE_OFFSET[dense] as usize;
let n = PAGE_OFFSET[dense + 1] as usize - lo;
let off = scan_end_low(lo, n, low_v);
if off < n {
let ss = RUN_START_STRIDE[lo + off];
let start_low = ss & 0x3F;
let stride_bit = ss >> 6;
if low_v >= start_low && ((low_v - start_low) & stride_bit) == 0 {
let mut buf = [0u8; 4];
c.encode_utf8(&mut buf);
let folded = u32::from_le_bytes(buf).wrapping_add(BYTE_DELTA[lo + off]);
let folded_bytes = folded.to_le_bytes();
let dest_len = utf8_len(folded_bytes[0]);
return core::str::from_utf8(&folded_bytes[..dest_len])
.expect("folded bytes are valid UTF-8")
.chars()
.next()
.expect("folded bytes encode exactly one char");
}
}
}
c
}
fn fold_into_bytes(s: String) -> Vec<u8> {
let mut bytes = s.into_bytes();
let mut high_bit_acc: u8 = 0;
for b in &mut bytes {
high_bit_acc |= *b;
let is_upper = b.wrapping_sub(b'A') < 26;
*b |= u8::from(is_upper) << 5;
}
if high_bit_acc & 0x80 == 0 {
return bytes;
}
let first_non_ascii = bytes
.iter()
.position(|&b| b & 0x80 != 0)
.expect("a non-ASCII byte exists (the high-bit accumulator was set)");
fold_non_ascii_tail(bytes, first_non_ascii)
}
fn fold_non_ascii_tail(bytes: Vec<u8>, start: usize) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
let src = bytes.as_ptr();
let mut dst: *mut u8 = core::ptr::null_mut();
let mut flushed = 0usize;
let mut read = start;
while read < bytes.len() {
let lead = bytes[read];
if lead & 0x80 == 0 {
read += 1;
continue;
}
let (word_idx, bit_idx, c_len) = if lead < 0xE0 {
(0usize, (lead & 0x1F) as u32, 2usize)
} else if lead < 0xF0 {
((lead & 0x0F) as usize, (bytes[read + 1] & 0x3F) as u32, 3)
} else {
(
(((lead & 0x07) as usize) << 6) | (bytes[read + 1] & 0x3F) as usize,
(bytes[read + 2] & 0x3F) as u32,
4,
)
};
if word_idx >= PAGE_BITMAP.len() || (PAGE_BITMAP[word_idx] >> bit_idx) & 1 == 0 {
read += c_len;
continue;
}
let low_v = bytes[read + c_len - 1] & 0x3F;
let dense = popcount_up_to(word_idx, bit_idx) as usize;
let lo = PAGE_OFFSET[dense] as usize;
let n = PAGE_OFFSET[dense + 1] as usize - lo;
let off = scan_end_low(lo, n, low_v);
let idx = if off < n {
let ss = RUN_START_STRIDE[lo + off];
let start_low = ss & 0x3F;
let stride_bit = ss >> 6;
if low_v < start_low || ((low_v - start_low) & stride_bit) != 0 {
read += c_len;
continue;
}
lo + off
} else {
read += c_len;
continue;
};
let raw = if read + 4 <= bytes.len() {
u32::from_le_bytes(bytes[read..read + 4].try_into().expect("4-byte slice"))
} else {
let mut w = [0u8; 4];
w[..c_len].copy_from_slice(&bytes[read..read + c_len]);
u32::from_le_bytes(w)
};
let word = raw & (u32::MAX >> ((4 - c_len) * 8));
let folded = word.wrapping_add(BYTE_DELTA[idx]);
let dest_len = utf8_len((folded & 0xFF) as u8);
if dst.is_null() {
out = Vec::with_capacity(bytes.len() + bytes.len() / 2 + 4);
dst = out.as_mut_ptr();
}
unsafe {
let run = read - flushed;
if run != 0 {
core::ptr::copy_nonoverlapping(src.add(flushed), dst, run);
dst = dst.add(run);
}
dst.cast::<u32>().write_unaligned(folded.to_le());
dst = dst.add(dest_len);
}
read += c_len;
flushed = read;
}
if dst.is_null() {
return bytes;
}
unsafe {
let tail = bytes.len() - flushed;
core::ptr::copy_nonoverlapping(src.add(flushed), dst, tail);
dst = dst.add(tail);
out.set_len(dst as usize - out.as_ptr() as usize);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::reference;
use std::collections::HashMap;
fn fold_oracle(r: &HashMap<u32, u32>, s: &str) -> Vec<u8> {
let mut out = String::new();
for c in s.chars() {
let cp = c as u32;
let folded = r.get(&cp).copied().unwrap_or(cp);
out.push(char::from_u32(folded).expect("reference fold is a valid char"));
}
out.into_bytes()
}
#[test]
fn fold_into_bytes_ascii() {
assert_eq!(fold_into_bytes(String::new()), b"");
assert_eq!(fold_into_bytes("Hello, WORLD!".into()), b"hello, world!");
assert_eq!(fold_into_bytes("abc 123 XYZ".into()), b"abc 123 xyz");
}
#[test]
fn simple_fold_returns_string() {
assert_eq!(simple_fold("Hello, WORLD!".to_string()), "hello, world!");
assert_eq!(simple_fold("ÜBER Größe".to_string()), "über größe");
assert_eq!(simple_fold("\u{212A}elvin".to_string()), "kelvin");
assert_eq!(simple_fold("abc\u{023A}".to_string()), "abc\u{2C65}");
assert_eq!(simple_fold("漢字 שלום".to_string()), "漢字 שלום");
}
#[test]
fn fold_into_bytes_ascii_then_utf8_handoff() {
assert_eq!(
fold_into_bytes("MIXED Größe TEXT".into()),
"mixed größe text".as_bytes(),
);
assert_eq!(fold_into_bytes("LORD \u{212A}elvin".into()), b"lord kelvin",);
assert_eq!(
fold_into_bytes("abc\u{023A}".into()),
"abc\u{2C65}".as_bytes(),
);
}
#[test]
fn fold_into_bytes_length_preserving_bmp() {
assert_eq!(fold_into_bytes("ÄÖÜ".into()), "äöü".as_bytes());
assert_eq!(fold_into_bytes("ΑΒΓ".into()), "αβγ".as_bytes());
assert_eq!(fold_into_bytes("漢字".into()), "漢字".as_bytes());
}
#[test]
fn fold_into_bytes_reuses_buffer_for_ascii_input() {
let s = "MIXED case AsCiI 12345".to_string();
let original_ptr = s.as_ptr();
let out = fold_into_bytes(s);
assert_eq!(out, b"mixed case ascii 12345");
assert_eq!(out.as_ptr(), original_ptr);
}
#[test]
fn fold_into_bytes_reuses_buffer_for_nonfolding_nonascii() {
let s = "HELLO 日本語 שלום WORLD".to_string();
let original_ptr = s.as_ptr();
let out = fold_into_bytes(s);
assert_eq!(out, "hello 日本語 שלום world".as_bytes());
assert_eq!(out.as_ptr(), original_ptr);
}
#[test]
fn fold_into_bytes_handles_shrinking_fold() {
assert_eq!(fold_into_bytes("\u{212A}elvin".into()), b"kelvin");
let out = fold_into_bytes("LORD \u{212A}elvin RULES".into());
assert_eq!(out, b"lord kelvin rules");
assert_eq!(fold_into_bytes("\u{2126}".into()), "\u{03C9}".as_bytes());
}
#[test]
fn fold_into_bytes_handles_growing_fold() {
assert_eq!(fold_into_bytes("\u{023A}".into()), "\u{2C65}".as_bytes());
assert_eq!(fold_into_bytes("\u{023E}".into()), "\u{2C66}".as_bytes());
let out = fold_into_bytes("ABC\u{023A}xyz".into());
assert_eq!(out, "abc\u{2C65}xyz".as_bytes());
let out = fold_into_bytes("ABC\u{023E}xyz".into());
assert_eq!(out, "abc\u{2C66}xyz".as_bytes());
let out = fold_into_bytes("\u{023A}\u{023E}".into());
assert_eq!(out, "\u{2C65}\u{2C66}".as_bytes());
let out = fold_into_bytes("Ä\u{212A}\u{023A}\u{023E}".into());
assert_eq!(out, "ä\u{006B}\u{2C65}\u{2C66}".as_bytes());
}
#[test]
fn fold_into_bytes_matches_reference_map() {
let r = reference();
let input = "Quick BROWN Fox 🦊 ÜBER Größe ΣΟΦΙΑ \u{0130}\u{023A}漢";
assert_eq!(fold_into_bytes(input.to_string()), fold_oracle(&r, input));
}
#[test]
fn fold_into_bytes_matches_reference_map_exhaustive() {
let r = reference();
let mut input = String::from("X");
for cp in 0x80..0x110000u32 {
if (0xD800..0xE000).contains(&cp) {
continue; }
input.push(char::from_u32(cp).expect("cp is a valid non-surrogate char"));
}
let expected = fold_oracle(&r, &input);
assert_eq!(fold_into_bytes(input), expected);
}
#[test]
fn simple_fold_char_examples() {
assert_eq!(simple_fold_char('A'), 'a');
assert_eq!(simple_fold_char('z'), 'z');
assert_eq!(simple_fold_char('!'), '!');
assert_eq!(simple_fold_char('Ü'), 'ü');
assert_eq!(simple_fold_char('Σ'), 'σ');
assert_eq!(simple_fold_char('\u{212A}'), 'k');
assert_eq!(simple_fold_char('\u{023A}'), '\u{2C65}');
assert_eq!(simple_fold_char('中'), '中');
assert_eq!(simple_fold_char('🦊'), '🦊');
}
#[test]
fn simple_fold_char_matches_simple_fold_exhaustive() {
for cp in 0u32..0x110000 {
if (0xD800..0xE000).contains(&cp) {
continue; }
let c = char::from_u32(cp).expect("cp is a valid non-surrogate char");
assert_eq!(
simple_fold_char(c).to_string(),
simple_fold(c.to_string()),
"cp {cp:#x}"
);
}
}
}