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());
    }
}