use crate::{DelimiterResult, classify_byte};
pub unsafe fn find_delimiters(haystack: &[u8]) -> DelimiterResult {
find_delimiters_safe(haystack)
}
#[inline]
pub fn find_delimiters_safe(haystack: &[u8]) -> DelimiterResult {
for (i, &b) in haystack.iter().enumerate() {
if is_delimiter(b) {
return DelimiterResult::Found { pos: i, byte: b };
}
}
DelimiterResult::NotFound
}
pub unsafe fn classify_bytes(input: &[u8]) -> Vec<u8> {
classify_bytes_safe(input)
}
#[inline]
pub fn classify_bytes_safe(input: &[u8]) -> Vec<u8> {
input.iter().map(|&b| classify_byte(b)).collect()
}
pub unsafe fn skip_whitespace(input: &[u8]) -> usize {
skip_whitespace_safe(input)
}
#[inline]
pub fn skip_whitespace_safe(input: &[u8]) -> usize {
input
.iter()
.position(|&b| !b.is_ascii_whitespace())
.unwrap_or(input.len())
}
pub unsafe fn compute_byte_mask(block: &[u8], byte: u8) -> u64 {
compute_byte_mask_safe(block, byte)
}
#[inline]
pub fn compute_byte_mask_safe(block: &[u8], byte: u8) -> u64 {
let mut mask = 0u64;
for (i, &b) in block.iter().take(64).enumerate() {
if b == byte {
mask |= 1u64 << i;
}
}
mask
}
pub unsafe fn compute_all_masks(block: &[u8]) -> crate::AllMasks {
compute_all_masks_safe(block)
}
#[inline]
pub fn compute_all_masks_safe(block: &[u8]) -> crate::AllMasks {
let mut masks = crate::AllMasks::default();
for (i, &b) in block.iter().take(64).enumerate() {
let bit = 1u64 << i;
match b {
b'<' => masks.lt |= bit,
b'>' => masks.gt |= bit,
b'"' => masks.quot |= bit,
b'\'' => masks.apos |= bit,
_ => {}
}
}
masks
}
#[inline(always)]
fn is_delimiter(b: u8) -> bool {
matches!(b, b'<' | b'>' | b'&' | b'"' | b'\'' | b'=' | b'/')
}
#[cfg(test)]
mod tests {
use super::*;
use crate::class;
#[test]
fn find_delimiters_lt() {
let input = b"hello <world>";
let result = unsafe { find_delimiters(input) };
assert_eq!(result, DelimiterResult::Found { pos: 6, byte: b'<' });
}
#[test]
fn find_delimiters_amp() {
let input = b"a & b";
let result = unsafe { find_delimiters(input) };
assert_eq!(result, DelimiterResult::Found { pos: 2, byte: b'&' });
}
#[test]
fn find_delimiters_none() {
let input = b"hello world";
let result = unsafe { find_delimiters(input) };
assert_eq!(result, DelimiterResult::NotFound);
}
#[test]
fn find_delimiters_empty() {
let result = unsafe { find_delimiters(b"") };
assert_eq!(result, DelimiterResult::NotFound);
}
#[test]
fn find_delimiters_first_byte() {
let result = unsafe { find_delimiters(b"<html>") };
assert_eq!(result, DelimiterResult::Found { pos: 0, byte: b'<' });
}
#[test]
fn find_delimiters_all_types() {
for &delim in b"<>&\"'=/" {
let input = [b'x', b'x', delim, b'x'];
let result = unsafe { find_delimiters(&input) };
assert_eq!(
result,
DelimiterResult::Found {
pos: 2,
byte: delim
},
"failed for delimiter 0x{delim:02X}"
);
}
}
#[test]
fn classify_bytes_mixed() {
let input = b"a1 <";
let result = unsafe { classify_bytes(input) };
assert_eq!(result[0], class::ALPHA); assert_eq!(result[1], class::DIGIT); assert_eq!(result[2], class::WHITESPACE); assert_eq!(result[3], class::DELIMITER); }
#[test]
fn classify_bytes_empty() {
let result = unsafe { classify_bytes(b"") };
assert!(result.is_empty());
}
#[test]
fn skip_whitespace_leading() {
let result = unsafe { skip_whitespace(b" hello") };
assert_eq!(result, 3);
}
#[test]
fn skip_whitespace_mixed() {
let result = unsafe { skip_whitespace(b" \t\n\rX") };
assert_eq!(result, 4);
}
#[test]
fn skip_whitespace_all() {
let result = unsafe { skip_whitespace(b" ") };
assert_eq!(result, 3);
}
#[test]
fn skip_whitespace_none() {
let result = unsafe { skip_whitespace(b"hello") };
assert_eq!(result, 0);
}
#[test]
fn skip_whitespace_empty() {
let result = unsafe { skip_whitespace(b"") };
assert_eq!(result, 0);
}
#[test]
fn compute_byte_mask_basic() {
let input = b"hello <world>";
let mask = unsafe { compute_byte_mask(input, b'<') };
assert_eq!(mask, 1 << 6);
}
#[test]
fn compute_byte_mask_multiple() {
let input = b"a<b<c";
let mask = unsafe { compute_byte_mask(input, b'<') };
assert_eq!(mask, (1 << 1) | (1 << 3));
}
#[test]
fn compute_byte_mask_none() {
let input = b"hello world";
let mask = unsafe { compute_byte_mask(input, b'<') };
assert_eq!(mask, 0);
}
#[test]
fn compute_byte_mask_empty() {
let mask = unsafe { compute_byte_mask(b"", b'<') };
assert_eq!(mask, 0);
}
#[test]
fn compute_byte_mask_over_64_bytes_does_not_overflow() {
let input = vec![b'<'; 80];
let mask = compute_byte_mask_safe(&input, b'<');
assert_eq!(mask, u64::MAX, "first 64 '<' set, rest ignored");
}
#[test]
fn compute_all_masks_over_64_bytes_does_not_overflow() {
let input = vec![b'<'; 80];
let masks = compute_all_masks_safe(&input);
assert_eq!(masks.lt, u64::MAX);
}
#[test]
fn compute_all_masks_basic() {
let input = b"<div class=\"foo\">";
let masks = unsafe { compute_all_masks(input) };
assert_eq!(masks.lt, 1 << 0); assert_eq!(masks.gt, 1 << 16); assert_eq!(masks.quot, (1 << 11) | (1 << 15)); }
#[test]
fn compute_all_masks_empty() {
let masks = unsafe { compute_all_masks(b"") };
assert_eq!(masks.lt, 0);
assert_eq!(masks.gt, 0);
}
#[test]
fn compute_all_masks_matches_individual() {
let input = b"Hello <World> & \"test\" = 'value' / 123\n\r\t end!!";
let masks = unsafe { compute_all_masks(input) };
assert_eq!(masks.lt, unsafe { compute_byte_mask(input, b'<') });
assert_eq!(masks.gt, unsafe { compute_byte_mask(input, b'>') });
assert_eq!(masks.quot, unsafe { compute_byte_mask(input, b'"') });
assert_eq!(masks.apos, unsafe { compute_byte_mask(input, b'\'') });
}
}