literal.rs raw

//! Literal string and byte sequence parsing.
//!
//! This module provides the [`Literal`] parser for matching exact byte sequences or strings.
//! It includes memory optimization that can store either
//! static string literals (zero allocation) or runtime-allocated byte sequences.
//!
//! The parser supports both string and byte array inputs, with convenient constructors
//! for compile-time constants that avoid heap allocation.

use std::{
    any::TypeId,
    hash::{DefaultHasher, Hash, Hasher},
};

use crate::{
    cache::ParsingCache,
    parser::{Parsable, Parser, Source},
    result::{Error, ParseResult},
};

/// A parser that matches a fixed sequence of bytes exactly.
///
/// The Literal parser consumes bytes from the input if and only if they match
/// the expected byte sequence exactly. It succeeds and returns the matched bytes
/// as a `Vec<u8>`, or fails if any byte doesn't match.
///
/// # Examples
///
/// ```rust
/// use neotoma::{literal::Literal, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// // Match the exact byte sequence [72, 101, 108, 108, 111] ("Hello")
/// let hello_bytes = Literal::from_bytes(b"Hello");
/// let mut input = Cursor::new(b"Hello world");
/// let mut source = Source::new(input);
/// let result = parse(hello_bytes, &mut source).unwrap();
/// assert_eq!(result, b"Hello".as_slice().into());
///
/// // Match the string "world" as UTF-8 bytes
/// let world = Literal::from_str("world");
/// let mut input = Cursor::new(b"world");
/// let mut source = Source::new(input);
/// let result = parse(world, &mut source).unwrap();
/// assert_eq!(result, b"world".as_slice().into());
///
/// // Match a specific protocol header
/// let header = Literal::from_bytes(&[0x89, 0x50, 0x4E, 0x47]); // PNG signature
/// let mut input = Cursor::new(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A]);
/// let mut source = Source::new(input);
/// let result = parse(header, &mut source).unwrap();
/// assert_eq!(result, [0x89, 0x50, 0x4E, 0x47].as_slice().into());
/// ```
#[derive(Clone, PartialEq, Eq)]
pub struct Literal {
    expected: LiteralData,
}

#[derive(Clone, PartialEq, Eq)]
enum LiteralData {
    Owned(Box<[u8]>),
    Static(&'static [u8]),
}

impl Literal {
    /// Create a new Literal parser from a byte slice.
    ///
    /// The parser will match the exact sequence of bytes provided.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let parser = Literal::from_bytes(b"HTTP/1.1");
    /// let mut input = Cursor::new(b"HTTP/1.1 200 OK");
    /// let mut source = Source::new(input);
    /// let result = parse(parser, &mut source).unwrap();
    /// // Matches exactly the bytes [72, 84, 84, 80, 47, 49, 46, 49]
    /// assert_eq!(result, b"HTTP/1.1".as_slice().into());
    /// ```
    pub fn from_bytes(bytes: &[u8]) -> Self {
        Self {
            expected: LiteralData::Owned(bytes.into()),
        }
    }

    /// Create a new Literal parser from a string reference.
    ///
    /// The string will be converted to UTF-8 bytes for matching.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let parser = Literal::from_str("Hello, world!");
    /// let mut input = Cursor::new(b"Hello, world! How are you?");
    /// let mut source = Source::new(input);
    /// let result = parse(parser, &mut source).unwrap();
    /// // Matches the UTF-8 byte encoding of "Hello, world!"
    /// assert_eq!(result, b"Hello, world!".as_slice().into());
    /// ```
    // This is consistent with our other constructor names, and nothing like what FromStr is meant for.
    #[allow(clippy::should_implement_trait)]
    pub fn from_str<S: AsRef<str>>(s: S) -> Self {
        Self {
            expected: LiteralData::Owned(s.as_ref().as_bytes().into()),
        }
    }

    /// Create a new Literal parser from a compile-time string literal.
    ///
    /// This is a const function that can be used to create Literal parsers
    /// at compile time for string literals.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// const HELLO_PARSER: Literal = Literal::from_str_const("hello");
    /// let mut input = Cursor::new(b"hello world");
    /// let mut source = Source::new(input);
    /// let result = parse(HELLO_PARSER, &mut source).unwrap();
    /// assert_eq!(result, b"hello".as_slice().into());
    /// ```
    pub const fn from_str_const(s: &'static str) -> Self {
        Self {
            expected: LiteralData::Static(s.as_bytes()),
        }
    }

    /// Create a new Literal parser from compile-time byte slice.
    ///
    /// This is a const function that can be used to create Literal parsers
    /// at compile time for byte literals.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// const PNG_HEADER: Literal = Literal::from_bytes_const(&[0x89, 0x50, 0x4E, 0x47]);
    /// let mut input = Cursor::new(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A]);
    /// let mut source = Source::new(input);
    /// let result = parse(PNG_HEADER, &mut source).unwrap();
    /// assert_eq!(result, [0x89, 0x50, 0x4E, 0x47].as_slice().into());
    /// ```
    pub const fn from_bytes_const(bytes: &'static [u8]) -> Self {
        Self {
            expected: LiteralData::Static(bytes),
        }
    }

    /// Get the expected byte sequence.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::literal::Literal;
    ///
    /// let parser = Literal::from_str("test");
    /// assert_eq!(parser.bytes(), b"test");
    /// ```
    pub fn bytes(&self) -> &[u8] {
        match &self.expected {
            LiteralData::Owned(bytes) => bytes,
            LiteralData::Static(bytes) => bytes,
        }
    }

    /// Get the length of the expected sequence.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::literal::Literal;
    ///
    /// let parser = Literal::from_str("hello");
    /// assert_eq!(parser.len(), 5);
    /// ```
    pub fn len(&self) -> usize {
        self.bytes().len()
    }

    /// Check if the literal is empty.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::literal::Literal;
    ///
    /// let empty = Literal::from_str("");
    /// assert!(empty.is_empty());
    ///
    /// let not_empty = Literal::from_str("a");
    /// assert!(!not_empty.is_empty());
    /// ```
    pub fn is_empty(&self) -> bool {
        self.bytes().is_empty()
    }
}

impl<Ctx> Parser<Ctx> for Literal {
    type Output = Box<[u8]>;

    fn id(&self) -> u64 {
        let mut hasher = DefaultHasher::new();
        TypeId::of::<Self>().hash(&mut hasher);
        self.bytes().hash(&mut hasher);
        hasher.finish()
    }

    fn read<S>(
        &self,
        source: &mut Source<S>,
        _cache: &mut impl ParsingCache,
        _context: &mut Ctx,
    ) -> ParseResult<Self::Output>
    where
        S: Parsable,
    {
        // Handle empty literal case
        if self.is_empty() {
            return Ok(Box::new([]));
        }

        let expected_bytes = self.bytes();

        // Try to read the expected number of bytes
        match source.peek(expected_bytes.len()) {
            Ok(bytes) => {
                // Check if the bytes match exactly
                if bytes == expected_bytes {
                    // Consume the matched bytes
                    source.advance(expected_bytes.len());
                    Ok(expected_bytes.into())
                } else {
                    // Bytes don't match
                    Err(Error::NoMatch)
                }
            }
            Err(Error::NoMatch) => {
                // Not enough bytes available (EOF or insufficient input)
                Err(Error::NoMatch)
            }
            Err(err) => Err(err),
        }
    }
}

// Convenience trait implementations for easier construction
impl From<&[u8]> for Literal {
    fn from(bytes: &[u8]) -> Self {
        Self::from_bytes(bytes)
    }
}

impl From<&str> for Literal {
    fn from(s: &str) -> Self {
        Self::from_str(s)
    }
}

impl From<String> for Literal {
    fn from(s: String) -> Self {
        Self::from_str(s)
    }
}

impl From<Vec<u8>> for Literal {
    fn from(bytes: Vec<u8>) -> Self {
        Self {
            expected: LiteralData::Owned(bytes.into_boxed_slice()),
        }
    }
}

impl From<Box<[u8]>> for Literal {
    fn from(bytes: Box<[u8]>) -> Self {
        Self {
            expected: LiteralData::Owned(bytes),
        }
    }
}

// Display and Debug implementations for better developer experience
impl std::fmt::Debug for Literal {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Try to display as string if it's valid UTF-8, otherwise as bytes
        let bytes = self.bytes();
        match std::str::from_utf8(bytes) {
            Ok(s) => write!(f, "Literal::from_str({s:?})"),
            Err(_) => write!(f, "Literal::from_bytes({bytes:?})"),
        }
    }
}

impl std::fmt::Display for Literal {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let bytes = self.bytes();
        match std::str::from_utf8(bytes) {
            Ok(s) => write!(f, "\"{s}\""),
            Err(_) => write!(f, "{bytes:?}"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_literal_from_str() {
        let literal = Literal::from_str("hello");
        assert_eq!(literal.bytes(), b"hello");
        assert_eq!(literal.len(), 5);
        assert!(!literal.is_empty());
    }

    #[test]
    fn test_literal_from_bytes() {
        let literal = Literal::from_bytes(b"\x89PNG");
        assert_eq!(literal.bytes(), &[0x89, 0x50, 0x4E, 0x47]);
        assert_eq!(literal.len(), 4);
    }

    #[test]
    fn test_empty_literal() {
        let literal = Literal::from_str("");
        assert!(literal.is_empty());
        assert_eq!(literal.len(), 0);
    }

    #[test]
    fn test_literal_conversion_traits() {
        let _from_str: Literal = "test".into();
        let _from_string: Literal = String::from("test").into();
        let _from_bytes: Literal = b"test".as_slice().into();
        let _from_vec: Literal = vec![116, 101, 115, 116].into();
    }

    #[test]
    fn test_literal_parsing_success() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("hello");
        let mut input = Cursor::new(b"hello world");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, b"hello".as_slice().into());
    }

    #[test]
    fn test_literal_parsing_failure() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("hello");
        let mut input = Cursor::new(b"world");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source);
        assert!(matches!(result, Err(Error::NoMatch)));
    }

    #[test]
    fn test_literal_parsing_partial_match() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("hello");
        let mut input = Cursor::new(b"hell");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source);
        assert!(matches!(result, Err(Error::NoMatch)));
    }

    #[test]
    fn test_literal_parsing_prefix_match() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("he");
        let mut input = Cursor::new(b"hello");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, b"he".as_slice().into());
    }

    #[test]
    fn test_literal_parsing_empty() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("");
        let mut input = Cursor::new(b"anything");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, b"".as_slice().into());
    }

    #[test]
    fn test_literal_parsing_empty_input() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("hello");
        let mut input = Cursor::new(b"");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source);
        assert!(matches!(result, Err(Error::NoMatch)));
    }

    #[test]
    fn test_literal_parsing_empty_on_empty() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("");
        let mut input = Cursor::new(b"");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, b"".as_slice().into());
    }

    #[test]
    fn test_literal_parsing_binary_data() {
        use crate::parser::parse;
        use std::io::Cursor;

        let png_header = Literal::from_bytes(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
        let mut input = Cursor::new(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00]);
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(png_header, &mut source).unwrap();
        assert_eq!(
            result,
            [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
                .as_slice()
                .into()
        );
    }

    #[test]
    fn test_literal_parsing_unicode() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("héllo"); // Contains accented character
        let input_bytes = "héllo world".as_bytes();
        let mut input = Cursor::new(input_bytes);
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, "héllo".as_bytes().into());
    }

    #[test]
    fn test_literal_clone() {
        let literal1 = Literal::from_str("test");
        let literal2 = literal1.clone();

        assert_eq!(literal1.bytes(), literal2.bytes());
        assert_eq!(literal1.len(), literal2.len());
    }

    #[test]
    fn test_literal_equality() {
        let literal1 = Literal::from_str("test");
        let literal2 = Literal::from_str("test");
        let literal3 = Literal::from_str("different");
        let literal4 = Literal::from_bytes(b"test");

        assert_eq!(literal1, literal2);
        assert_eq!(literal1, literal4); // String and bytes should be equal if same content
        assert_ne!(literal1, literal3);
    }

    #[test]
    fn test_literal_debug_format() {
        let string_literal = Literal::from_str("hello");
        let debug_str = format!("{string_literal:?}");
        assert_eq!(debug_str, "Literal::from_str(\"hello\")");

        let binary_literal = Literal::from_bytes(&[0x89, 0x50]);
        let debug_str = format!("{binary_literal:?}");
        assert_eq!(debug_str, "Literal::from_bytes([137, 80])");
    }

    #[test]
    fn test_literal_display_format() {
        let string_literal = Literal::from_str("hello");
        let display_str = format!("{string_literal}");
        assert_eq!(display_str, "\"hello\"");

        let binary_literal = Literal::from_bytes(&[0x89, 0x50]);
        let display_str = format!("{binary_literal}");
        assert_eq!(display_str, "[137, 80]");
    }

    #[test]
    fn test_literal_large_input() {
        use crate::parser::parse;
        use std::io::Cursor;

        let large_string = "x".repeat(1000);
        let literal = Literal::from_str(&large_string);
        let input_data = format!("{large_string}more");
        let mut input = Cursor::new(input_data.as_bytes());
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, large_string.as_bytes().into());
    }

    #[test]
    fn test_literal_from_box() {
        let boxed_bytes: Box<[u8]> = Box::new([1, 2, 3, 4]);
        let literal = Literal::from(boxed_bytes);
        assert_eq!(literal.bytes(), &[1, 2, 3, 4]);
    }

    #[test]
    fn test_literal_single_byte() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_bytes(b"A");
        let mut input = Cursor::new(b"ABCD");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, b"A".as_slice().into());
    }

    #[test]
    fn test_literal_case_sensitive() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("Hello");
        let mut input = Cursor::new(b"hello");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source);
        assert!(matches!(result, Err(Error::NoMatch)));
    }

    #[test]
    fn test_literal_newlines_and_whitespace() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_str("hello\nworld\t!");
        let mut input = Cursor::new(b"hello\nworld\t!more");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, b"hello\nworld\t!".as_slice().into());
    }

    #[test]
    fn test_literal_null_bytes() {
        use crate::parser::parse;
        use std::io::Cursor;

        let literal = Literal::from_bytes(&[b'a', 0, b'b']);
        let mut input = Cursor::new(&[b'a', 0, b'b', b'c']);
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(literal, &mut source).unwrap();
        assert_eq!(result, [b'a', 0, b'b'].as_slice().into());
    }

    #[test]
    fn test_literal_max_length() {
        let max_bytes = vec![255u8; 10000];
        let literal = Literal::from_bytes(&max_bytes);
        assert_eq!(literal.len(), 10000);
        assert_eq!(literal.bytes(), &max_bytes);
    }
}