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