//! 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`, 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: 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 Parser for Literal { type Output = Box<[u8]>; fn id(&self) -> u64 { let mut hasher = DefaultHasher::new(); TypeId::of::().hash(&mut hasher); self.bytes().hash(&mut hasher); hasher.finish() } fn read( &self, source: &mut Source, _cache: &mut impl ParsingCache, _context: &mut Ctx, ) -> ParseResult 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 for Literal { fn from(s: String) -> Self { Self::from_str(s) } } impl From> for Literal { fn from(bytes: Vec) -> Self { Self { expected: LiteralData::Owned(bytes.into_boxed_slice()), } } } impl From> 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); } }