pub const ASCII_CASE_OFFSET: u8 = 0x20;
#[inline]
pub fn fold_ascii_lowercase(b: u8) -> u8 {
fold_byte(b)
}
#[inline]
pub fn fold_byte(b: u8) -> u8 {
let is_lowercase = u8::from(b.wrapping_sub(b'a') < 26);
b.wrapping_sub(ASCII_CASE_OFFSET & is_lowercase.wrapping_neg())
}
#[inline]
pub fn fold_slice(data: &mut [u8]) {
for item in data.iter_mut() {
*item = fold_byte(*item);
}
}
#[inline]
pub fn verify_exact(pattern: &[u8], haystack: &[u8]) -> bool {
pattern == haystack
}
#[inline]
pub fn verify_case_insensitive(pattern: &[u8], haystack: &[u8]) -> bool {
if pattern.len() != haystack.len() {
return false;
}
pattern
.iter()
.zip(haystack.iter())
.all(|(&pat, &h)| fold_byte(pat) == fold_byte(h))
}
#[cfg(test)]
mod tests {
use super::{fold_byte, fold_slice, verify_case_insensitive, verify_exact};
#[test]
fn fold_byte_classifies_ascii_ranges() {
for c in b'a'..=b'z' {
assert_eq!(fold_byte(c), c - 0x20);
}
for c in b'A'..=b'Z' {
assert_eq!(fold_byte(c), c);
}
for c in b'0'..=b'9' {
assert_eq!(fold_byte(c), c);
}
for c in [b'!', b'-', b'/', b':', b'`', b'{', b'~'] {
assert_eq!(fold_byte(c), c);
}
}
#[test]
fn verify_case_insensitive_matches_folded_exact() {
let pattern = b"AbC-9!xY";
let haystack = b"aBc-9!Xy";
let mut folded_pattern = pattern.to_vec();
let mut folded_haystack = haystack.to_vec();
fold_slice(&mut folded_pattern);
fold_slice(&mut folded_haystack);
assert_eq!(
verify_case_insensitive(pattern, haystack),
verify_exact(&folded_pattern, &folded_haystack)
);
}
}