use crate::table::*;
use crate::{popcount_up_to, scan_end_low};
pub fn index_fold(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)");
let mut write = first_non_ascii;
let mut read = first_non_ascii;
while read < bytes.len() {
let lead = bytes[read];
if lead & 0x80 == 0 {
bytes[write] = lead;
write += 1;
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,
)
};
let low_v = bytes[read + c_len - 1] & 0x3F;
let mut folded_index = ((bit_idx << 6) as u8) | low_v;
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 {
folded_index = folded_index.wrapping_add(INDEX_DELTA[lo + off]);
}
}
}
bytes[write] = 0x80 | folded_index;
write += 1;
read += c_len;
}
bytes.truncate(write);
bytes
}
pub fn index_fold_char(c: char) -> u8 {
let cp = c as u32;
if cp < 0x80 {
let b = cp as u8;
let is_upper = b.wrapping_sub(b'A') < 26;
return 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;
let mut folded_index = (cp & 0x7F) 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 {
folded_index = folded_index.wrapping_add(INDEX_DELTA[lo + off]);
}
}
}
0x80 | folded_index
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::reference;
use std::collections::HashMap;
fn index_fold_oracle(r: &HashMap<u32, u32>, s: &str) -> Vec<u8> {
let mut out = Vec::new();
for c in s.chars() {
let cp = c as u32;
let folded = r.get(&cp).copied().unwrap_or(cp);
if cp < 0x80 {
out.push(folded as u8);
} else {
out.push(0x80 | (folded & 0x7F) as u8);
}
}
out
}
#[test]
fn index_fold_ascii() {
assert_eq!(index_fold(String::new()), b"");
assert_eq!(index_fold("Hello, WORLD!".into()), b"hello, world!");
assert_eq!(index_fold("abc 123 XYZ".into()), b"abc 123 xyz");
}
#[test]
fn index_fold_reuses_buffer_for_ascii_input() {
let s = "MIXED case AsCiI 12345".to_string();
let original_ptr = s.as_ptr();
let out = index_fold(s);
assert_eq!(out, b"mixed case ascii 12345");
assert_eq!(out.as_ptr(), original_ptr);
}
#[test]
fn index_fold_multibyte_to_single_byte() {
assert_eq!(index_fold("Ü".into()), vec![0xFC]);
assert_eq!(
index_fold("ÄÖÜ".into()),
vec![0x80 | 0x64, 0x80 | 0x76, 0xFC]
);
assert_eq!(
index_fold("\u{212A}elvin".into()),
vec![0x80 | b'k', b'e', b'l', b'v', b'i', b'n'],
);
assert_eq!(index_fold("\u{023A}".into()), vec![0x80 | 0x65]);
assert_eq!(index_fold("中".into()), vec![0x80 | 0x2D]);
}
#[test]
fn index_fold_matches_reference_map() {
let r = reference();
let input = "Quick BROWN Fox 🦊 ÜBER Größe ΣΟΦΙΑ \u{0130}\u{023A}漢";
assert_eq!(index_fold(input.to_string()), index_fold_oracle(&r, input));
}
#[test]
fn index_fold_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 = index_fold_oracle(&r, &input);
assert_eq!(index_fold(input), expected);
}
#[test]
fn index_fold_char_examples() {
assert_eq!(index_fold_char('A'), b'a');
assert_eq!(index_fold_char('!'), b'!');
assert_eq!(index_fold_char('Ü'), 0xFC);
assert_eq!(index_fold_char('中'), 0x80 | 0x2D);
assert_eq!(index_fold_char('\u{212A}'), 0x80 | b'k');
}
#[test]
fn index_fold_char_matches_index_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");
let folded = index_fold(c.to_string());
assert_eq!(folded.len(), 1, "cp {cp:#x} did not yield one byte");
assert_eq!(index_fold_char(c), folded[0], "cp {cp:#x}");
}
}
}