class.rs
raw
//! Byte-level character class parsing.
//!
//! This module provides the [`Class`] parser for matching sequences of bytes
//! that belong to a specific character class. It supports both predicate-based
//! matching and set-based matching for efficient byte classification.
//!
//! The Class parser is optimized for ASCII and binary data processing, with
//! built-in support for common character classes like digits, alphabetic
//! characters, alphanumeric, and whitespace. Custom predicates allow for
//! flexible byte matching logic.
use crate::{
cache::ParsingCache,
parser::{Parsable, Parser, Source},
result::{Error, ParseResult},
};
/// A parser that matches a sequence of bytes that are all present in a character class.
///
/// The Class parser consumes bytes from the input as long as each byte is present
/// in the specified set. It returns a `Vec<u8>` containing all matched bytes.
/// The parser succeeds even if it matches zero bytes (unless a minimum length is specified).
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// // Match digits: parses "123" from "123abc"
/// let digits = Class::new(b"0123456789");
/// let mut input1 = Cursor::new(b"123abc");
/// let mut source1 = Source::new(input1);
/// let result1 = parse(digits, &mut source1).unwrap();
/// assert_eq!(result1, b"123".to_vec());
///
/// // Match whitespace
/// let whitespace = Class::new(b" \t\r\n");
/// let mut input2 = Cursor::new(b" \t\r\nabc");
/// let mut source2 = Source::new(input2);
/// let result2 = parse(whitespace, &mut source2).unwrap();
/// assert_eq!(result2, b" \t\r\n".to_vec());
///
/// // Match hex digits
/// let hex = Class::new(b"0123456789abcdefABCDEF");
/// let mut input3 = Cursor::new(b"1a2bXYZ");
/// let mut source3 = Source::new(input3);
/// let result3 = parse(hex, &mut source3).unwrap();
/// assert_eq!(result3, b"1a2b".to_vec());
/// ```
#[derive(Clone)]
pub struct Class<F = fn(u8) -> bool> {
allowed: [bool; 256],
predicate: Option<F>,
min_length: usize,
max_length: Option<usize>,
}
impl Class<fn(u8) -> bool> {
/// Create a new Class parser that matches any byte in the given slice.
///
/// The parser will match zero or more bytes that are present in the class.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let digits = Class::new(b"0123456789");
///
/// // Matches: "", "1", "123", "999999", etc.
/// let mut input1 = Cursor::new(b"123abc");
/// let mut source1 = Source::new(input1);
/// let result1 = parse(digits, &mut source1).unwrap();
/// assert_eq!(result1, b"123".to_vec());
///
/// // Stops at first non-digit
/// let digits2 = Class::new(b"0123456789");
/// let mut input2 = Cursor::new(b"abc123");
/// let mut source2 = Source::new(input2);
/// let result2 = parse(digits2, &mut source2).unwrap();
/// assert_eq!(result2, Vec::<u8>::new()); // matches empty string
/// ```
pub fn new(bytes: &[u8]) -> Self {
let mut allowed = [false; 256];
for &byte in bytes {
allowed[byte as usize] = true;
}
Self {
allowed,
predicate: None,
min_length: 0,
max_length: None,
}
}
/// Create a new Class parser with a minimum required length.
///
/// The parser must match at least `min_length` bytes or it fails.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let digits = Class::with_min(b"0123456789", 2);
///
/// // Matches: "12", "999", "12345", etc.
/// let mut input1 = Cursor::new(b"123abc");
/// let mut source1 = Source::new(input1);
/// let result1 = parse(digits, &mut source1).unwrap();
/// assert_eq!(result1, b"123".to_vec());
///
/// // Fails on: "", "1"
/// let digits2 = Class::with_min(b"0123456789", 2);
/// let mut input2 = Cursor::new(b"1abc");
/// let mut source2 = Source::new(input2);
/// let result2 = parse(digits2, &mut source2);
/// assert!(result2.is_err()); // fails because only 1 digit
/// ```
pub fn with_min(bytes: &[u8], min_length: usize) -> Self {
let mut allowed = [false; 256];
for &byte in bytes {
allowed[byte as usize] = true;
}
Self {
allowed,
predicate: None,
min_length,
max_length: None,
}
}
/// Create a new Class parser with a maximum length limit.
///
/// The parser will stop after matching `max_length` bytes, even if more
/// matching bytes are available.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let digits = Class::with_max(b"0123456789", 3);
///
/// // From "12345", matches "123" and stops
/// let mut input = Cursor::new(b"12345abc");
/// let mut source = Source::new(input);
/// let result = parse(digits, &mut source).unwrap();
/// assert_eq!(result, b"123".to_vec());
/// ```
pub fn with_max(bytes: &[u8], max_length: usize) -> Self {
let mut allowed = [false; 256];
for &byte in bytes {
allowed[byte as usize] = true;
}
Self {
allowed,
predicate: None,
min_length: 0,
max_length: Some(max_length),
}
}
/// Create a new Class parser with both minimum and maximum length limits.
///
/// The parser must match at least `min_length` bytes and will stop after
/// `max_length` bytes.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let digits = Class::with_bounds(b"0123456789", 2, 4);
///
/// // Matches 2-4 digits: "12", "123", "1234"
/// let mut input1 = Cursor::new(b"123abc");
/// let mut source1 = Source::new(input1);
/// let result1 = parse(digits, &mut source1).unwrap();
/// assert_eq!(result1, b"123".to_vec());
///
/// // Stops at 4 even from "123456"
/// let digits2 = Class::with_bounds(b"0123456789", 2, 4);
/// let mut input2 = Cursor::new(b"123456abc");
/// let mut source2 = Source::new(input2);
/// let result2 = parse(digits2, &mut source2).unwrap();
/// assert_eq!(result2, b"1234".to_vec());
/// ```
pub fn with_bounds(bytes: &[u8], min_length: usize, max_length: usize) -> Self {
let mut allowed = [false; 256];
for &byte in bytes {
allowed[byte as usize] = true;
}
Self {
allowed,
predicate: None,
min_length,
max_length: Some(max_length),
}
}
/// Create a Class parser for ASCII digits (0-9).
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let digits = Class::digits();
/// // Equivalent to: Class::with_min(b"0123456789", 1)
///
/// let mut input = Cursor::new(b"123abc");
/// let mut source = Source::new(input);
/// let result = parse(digits, &mut source).unwrap();
/// assert_eq!(result, b"123".to_vec());
/// ```
pub fn digits() -> Self {
Self::with_min(b"0123456789", 1)
}
/// Create a Class parser for ASCII alphabetic characters (a-z, A-Z).
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let alpha = Class::alpha();
///
/// // Matches: "abc", "XYZ", "Hello", etc.
/// let mut input = Cursor::new(b"Hello123");
/// let mut source = Source::new(input);
/// let result = parse(alpha, &mut source).unwrap();
/// assert_eq!(result, b"Hello".to_vec());
/// ```
pub fn alpha() -> Self {
Self::with_min(b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 1)
}
/// Create a Class parser for ASCII alphanumeric characters (a-z, A-Z, 0-9).
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let alnum = Class::alphanumeric();
///
/// // Matches: "abc123", "Hello42", etc.
/// let mut input = Cursor::new(b"Hello42_world");
/// let mut source = Source::new(input);
/// let result = parse(alnum, &mut source).unwrap();
/// assert_eq!(result, b"Hello42".to_vec());
/// ```
pub fn alphanumeric() -> Self {
Self::with_min(
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
1,
)
}
/// Create a Class parser for ASCII whitespace characters.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let whitespace = Class::whitespace();
///
/// // Matches: " ", "\t\n", " ", etc.
/// let mut input = Cursor::new(b" \t\r\nhello");
/// let mut source = Source::new(input);
/// let result = parse(whitespace, &mut source).unwrap();
/// assert_eq!(result, b" \t\r\n".to_vec());
/// ```
pub fn whitespace() -> Self {
Self::with_min(b" \t\r\n\x0b\x0c", 1)
}
/// Create a Class parser for hexadecimal digits (0-9, a-f, A-F).
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let hex = Class::hex_digits();
///
/// // Matches: "1a2b", "DEADBEEF", "0xff", etc.
/// let mut input = Cursor::new(b"DEADBEEFghij");
/// let mut source = Source::new(input);
/// let result = parse(hex, &mut source).unwrap();
/// assert_eq!(result, b"DEADBEEF".to_vec());
/// ```
pub fn hex_digits() -> Self {
Self::with_min(b"0123456789abcdefABCDEF", 1)
}
}
impl<F> Class<F>
where
F: Fn(u8) -> bool,
{
/// Create a new Class parser that uses a predicate function to test bytes.
///
/// The predicate function will be called for each byte to determine if it
/// should be matched. This allows for complex matching logic.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// // Match ASCII letters (both cases)
/// let letters = Class::from_predicate(|b| b.is_ascii_alphabetic());
/// let mut input1 = Cursor::new(b"Hello123");
/// let mut source1 = Source::new(input1);
/// let result1 = parse(letters, &mut source1).unwrap();
/// assert_eq!(result1, b"Hello".to_vec());
///
/// // Match even bytes
/// let even = Class::from_predicate(|b| b % 2 == 0);
/// let mut input2 = Cursor::new(&[2u8, 4u8, 6u8, 1u8, 8u8]);
/// let mut source2 = Source::new(input2);
/// let result2 = parse(even, &mut source2).unwrap();
/// assert_eq!(result2, vec![2u8, 4u8, 6u8]);
/// ```
pub fn from_predicate(predicate: F) -> Self {
Self {
allowed: [false; 256], // Ignored when predicate is present
predicate: Some(predicate),
min_length: 0,
max_length: None,
}
}
/// Create a Class parser with a predicate and minimum length.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let letters = Class::from_predicate_min(|b| b.is_ascii_alphabetic(), 2);
///
/// // Must match at least 2 letters
/// let mut input = Cursor::new(b"Hello123");
/// let mut source = Source::new(input);
/// let result = parse(letters, &mut source).unwrap();
/// assert_eq!(result, b"Hello".to_vec());
/// ```
pub fn from_predicate_min(predicate: F, min_length: usize) -> Self {
Self {
allowed: [false; 256],
predicate: Some(predicate),
min_length,
max_length: None,
}
}
/// Create a Class parser with a predicate and maximum length.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let letters = Class::from_predicate_max(|b| b.is_ascii_alphabetic(), 5);
///
/// // Match at most 5 letters
/// let mut input = Cursor::new(b"HelloWorld123");
/// let mut source = Source::new(input);
/// let result = parse(letters, &mut source).unwrap();
/// assert_eq!(result, b"Hello".to_vec()); // stops at 5 letters
/// ```
pub fn from_predicate_max(predicate: F, max_length: usize) -> Self {
Self {
allowed: [false; 256],
predicate: Some(predicate),
min_length: 0,
max_length: Some(max_length),
}
}
/// Create a Class parser with a predicate and both min/max length.
///
/// # Examples
///
/// ```rust
/// use neotoma::{class::Class, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let letters = Class::from_predicate_bounds(|b| b.is_ascii_alphabetic(), 2, 5);
///
/// // Match 2-5 letters
/// let mut input = Cursor::new(b"HelloWorld123");
/// let mut source = Source::new(input);
/// let result = parse(letters, &mut source).unwrap();
/// assert_eq!(result, b"Hello".to_vec()); // matches 5 letters then stops
/// ```
pub fn from_predicate_bounds(predicate: F, min_length: usize, max_length: usize) -> Self {
Self {
allowed: [false; 256],
predicate: Some(predicate),
min_length,
max_length: Some(max_length),
}
}
/// Check if a byte matches this class
fn byte_matches(&self, byte: u8) -> bool {
if let Some(ref predicate) = self.predicate {
predicate(byte)
} else {
self.allowed[byte as usize]
}
}
}
impl<F, Ctx> Parser<Ctx> for Class<F>
where
F: Fn(u8) -> bool + 'static,
{
type Output = Vec<u8>;
fn id(&self) -> u64 {
use std::any::TypeId;
use std::hash::{DefaultHasher, Hash, Hasher};
let mut hasher = DefaultHasher::new();
TypeId::of::<Self>().hash(&mut hasher);
// Hash the allowed array for character set-based classes
self.allowed.hash(&mut hasher);
self.min_length.hash(&mut hasher);
self.max_length.hash(&mut hasher);
// Note: predicate functions can't be hashed directly, but the allowed array
// captures the essential state for non-predicate classes
hasher.finish()
}
fn read<S>(
&self,
source: &mut Source<S>,
_cache: &mut impl ParsingCache,
_context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
let mut result = Vec::new();
loop {
// Check max length limit
if let Some(max_length) = self.max_length {
if result.len() >= max_length {
break;
}
}
// Try to peek the next byte
match source.peek1() {
Ok(byte) => {
if self.byte_matches(byte) {
// Byte is in our class, consume it
result.push(byte);
source.advance(1);
} else {
// Byte not in class, stop matching
break;
}
}
Err(Error::NoMatch) => {
// End of input, stop matching
break;
}
Err(err) => return Err(err),
}
}
// Check minimum length requirement
if result.len() < self.min_length {
return Err(Error::NoMatch);
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse;
use std::io::Cursor;
#[test]
fn test_id_implementation_different_class_parsers() {
// Test that Class implements proper id() method
// Different Class parsers should have different IDs to avoid cache conflicts
let class1 = Class::new(b"abc");
let class2 = Class::new(b"xyz");
// These should have different IDs because they have different byte sets
// This test will FAIL if Class uses default id() implementation
assert_ne!(
<Class as crate::parser::Parser<()>>::id(&class1),
<Class as crate::parser::Parser<()>>::id(&class2),
"Different Class instances should have different IDs to avoid cache collisions"
);
}
#[test]
fn test_id_implementation_same_class_parsers() {
// Test that identical Class parsers have the same ID
let class1 = Class::new(b"abc");
let class2 = Class::new(b"abc");
assert_eq!(
<Class as crate::parser::Parser<()>>::id(&class1),
<Class as crate::parser::Parser<()>>::id(&class2),
"Identical Class instances should have the same ID for cache efficiency"
);
}
#[test]
fn test_id_implementation_class_different_bounds() {
// Test Class parsers with different bounds
let class1 = Class::with_min(b"abc", 1);
let class2 = Class::with_min(b"abc", 2);
// These should have different IDs because they have different minimum bounds
// This test will FAIL if Class uses default id() implementation
assert_ne!(
<Class as crate::parser::Parser<()>>::id(&class1),
<Class as crate::parser::Parser<()>>::id(&class2),
"Class instances with different bounds should have different IDs"
);
}
#[test]
fn test_id_implementation_class_predicate_parsers() {
// Test Class parsers with predicates
let class1 = Class::from_predicate(|b| b.is_ascii_alphabetic());
let class2 = Class::from_predicate(|b| b.is_ascii_digit());
// These should have different IDs because they have different predicates
// This test will FAIL if Class uses default id() implementation
// Helper function to get ID with proper type inference
fn get_id<P: crate::parser::Parser<()>>(parser: &P) -> u64 {
parser.id()
}
assert_ne!(
get_id(&class1),
get_id(&class2),
"Class instances with different predicates should have different IDs"
);
}
#[test]
fn test_class_basic_functionality() {
// Basic functionality test to ensure Class works correctly
let class = Class::new(b"abc");
let mut input = Cursor::new(b"aabbcc123");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(class, &mut source).unwrap();
assert_eq!(result, b"aabbcc".to_vec());
}
#[test]
fn test_class_empty_character_set() {
// Test behavior with empty character set
let class = Class::new(b"");
let mut input = Cursor::new(b"abc");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(class, &mut source).unwrap();
assert_eq!(result, Vec::<u8>::new()); // Should match empty string
}
#[test]
fn test_class_boundary_conditions() {
// Test min boundary exactly
let class = Class::with_min(b"abc", 3);
let mut input1 = Cursor::new(b"abc");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(class, &mut source1).unwrap();
assert_eq!(result1, b"abc".to_vec());
// Test min boundary failure
let class2 = Class::with_min(b"abc", 4);
let mut input2 = Cursor::new(b"abc");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(class2, &mut source2);
assert!(result2.is_err());
// Test max boundary exactly
let class3 = Class::with_max(b"abc", 2);
let mut input3 = Cursor::new(b"abcabc");
let mut source3 = crate::parser::Source::new(&mut input3);
let result3 = parse(class3, &mut source3).unwrap();
assert_eq!(result3, b"ab".to_vec());
}
#[test]
fn test_class_non_ascii_bytes() {
// Test with bytes > 127
let class = Class::new(&[0xFF, 0xFE, 0xFD]);
let mut input = Cursor::new(&[0xFF, 0xFE, 0xFD, 0xFC, 0x00]);
let mut source = crate::parser::Source::new(&mut input);
let result = parse(class, &mut source).unwrap();
assert_eq!(result, vec![0xFF, 0xFE, 0xFD]);
}
#[test]
fn test_class_position_tracking() {
// Verify position is correctly advanced
let class = Class::new(b"abc");
let mut input = Cursor::new(b"abcXYZ");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(class, &mut source).unwrap();
assert_eq!(result, b"abc".to_vec());
// Position should be at 'X'
let next_byte = source.peek1().unwrap();
assert_eq!(next_byte, b'X');
}
#[test]
fn test_class_convenience_constructors() {
// Test digits()
let digits = Class::digits();
let mut input1 = Cursor::new(b"123abc");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(digits, &mut source1).unwrap();
assert_eq!(result1, b"123".to_vec());
// Test alpha()
let alpha = Class::alpha();
let mut input2 = Cursor::new(b"Hello123");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(alpha, &mut source2).unwrap();
assert_eq!(result2, b"Hello".to_vec());
// Test alphanumeric()
let alnum = Class::alphanumeric();
let mut input3 = Cursor::new(b"Hello123_world");
let mut source3 = crate::parser::Source::new(&mut input3);
let result3 = parse(alnum, &mut source3).unwrap();
assert_eq!(result3, b"Hello123".to_vec());
}
#[test]
fn test_class_predicate_edge_cases() {
// Predicate that always returns true
let always_true = Class::from_predicate(|_| true);
let mut input1 = Cursor::new(b"abc");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(always_true, &mut source1).unwrap();
assert_eq!(result1, b"abc".to_vec());
// Predicate that always returns false
let always_false = Class::from_predicate(|_| false);
let mut input2 = Cursor::new(b"abc");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(always_false, &mut source2).unwrap();
assert_eq!(result2, Vec::<u8>::new());
// Predicate with minimum requirement that always returns false
let always_false_min = Class::from_predicate_min(|_| false, 1);
let mut input3 = Cursor::new(b"abc");
let mut source3 = crate::parser::Source::new(&mut input3);
let result3 = parse(always_false_min, &mut source3);
assert!(result3.is_err());
}
#[test]
fn test_class_empty_input() {
// Test with empty input
let class = Class::new(b"abc");
let mut input = Cursor::new(b"");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(class, &mut source).unwrap();
assert_eq!(result, Vec::<u8>::new());
// Test with minimum requirement on empty input
let class_min = Class::with_min(b"abc", 1);
let mut input2 = Cursor::new(b"");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(class_min, &mut source2);
assert!(result2.is_err());
}
#[test]
fn test_class_large_character_set() {
// Test with all possible bytes
let all_bytes: Vec<u8> = (0..=255).collect();
let class = Class::new(&all_bytes);
let mut input = Cursor::new(b"Hello\xFF\xFE\xFD123");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(class, &mut source).unwrap();
assert_eq!(result, b"Hello\xFF\xFE\xFD123".to_vec());
}
}