//! 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` 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 bool> { allowed: [bool; 256], predicate: Option, min_length: usize, max_length: Option, } impl Class 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::::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 Class 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 Parser for Class where F: Fn(u8) -> bool + 'static, { type Output = Vec; fn id(&self) -> u64 { use std::any::TypeId; use std::hash::{DefaultHasher, Hash, Hasher}; let mut hasher = DefaultHasher::new(); TypeId::of::().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( &self, source: &mut Source, _cache: &mut impl ParsingCache, _context: &mut Ctx, ) -> ParseResult 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!( >::id(&class1), >::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!( >::id(&class1), >::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!( >::id(&class1), >::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>(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::::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::::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::::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 = (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()); } }