//! Optional parser combinator for zero-or-one matching. //! //! This module provides the [`Optional`] parser combinator that wraps another parser //! to make it succeed even when the wrapped parser fails. The result is wrapped in //! an `Option` where `Some(result)` indicates success and `None` indicates the //! wrapped parser failed with `NoMatch`. //! //! Optional parsers are essential for building flexible grammars where certain //! elements may or may not be present in the input. use crate::{ cache::ParsingCache, parser::{Parsable, Parser, Source}, result::{Error, ParseResult}, }; /// A parser combinator that optionally matches another parser. /// /// Optional applies a contained parser exactly once, returning `Some(result)` if it succeeds /// or `None` if it fails with `NoMatch`. The Optional parser itself always succeeds, /// making it useful for optional elements in a grammar. /// /// # Examples /// /// ```rust /// use neotoma::{optional::Optional, literal::Literal, parser::{parse, Source}}; /// use std::io::Cursor; /// /// // Optional "http://" prefix /// let http_prefix = Optional::new(Literal::from_str("http://")); /// /// // Matches: "http://example.com" -> Some("http://") /// let mut input1 = Cursor::new(b"http://example.com"); /// let mut source1 = Source::new(input1); /// let result1 = parse(http_prefix, &mut source1).unwrap(); /// assert_eq!(result1, Some(b"http://".as_slice().into())); /// /// // Matches: "example.com" -> None /// let http_prefix2 = Optional::new(Literal::from_str("http://")); /// let mut input2 = Cursor::new(b"example.com"); /// let mut source2 = Source::new(input2); /// let result2 = parse(http_prefix2, &mut source2).unwrap(); /// assert_eq!(result2, None); /// /// // Optional sign for numbers /// let sign = Optional::new(Literal::from_str("-")); /// /// // Matches: "-123" -> Some("-") /// let mut input3 = Cursor::new(b"-123"); /// let mut source3 = Source::new(input3); /// let result3 = parse(sign, &mut source3).unwrap(); /// assert_eq!(result3, Some(b"-".as_slice().into())); /// /// // Matches: "123" -> None /// let sign2 = Optional::new(Literal::from_str("-")); /// let mut input4 = Cursor::new(b"123"); /// let mut source4 = Source::new(input4); /// let result4 = parse(sign2, &mut source4).unwrap(); /// assert_eq!(result4, None); /// ``` #[derive(Clone)] pub struct Optional

{ parser: P, } impl

Optional

{ /// Create a new Optional parser that wraps another parser. /// /// The wrapped parser will be tried once. If it succeeds, `Some(result)` is returned. /// If it fails with `NoMatch`, `None` is returned. Other errors are propagated. /// /// # Examples /// /// ```rust /// use neotoma::{optional::Optional, literal::Literal, parser::{parse, Source}}; /// use std::io::Cursor; /// /// let optional_comma = Optional::new(Literal::from_str(",")); /// /// // Matches "," -> Some(",") /// let mut input1 = Cursor::new(b",hello"); /// let mut source1 = Source::new(input1); /// let result1 = parse(optional_comma, &mut source1).unwrap(); /// assert_eq!(result1, Some(b",".as_slice().into())); /// /// // Matches "abc" -> None (doesn't consume any input) /// let optional_comma2 = Optional::new(Literal::from_str(",")); /// let mut input2 = Cursor::new(b"abc"); /// let mut source2 = Source::new(input2); /// let result2 = parse(optional_comma2, &mut source2).unwrap(); /// assert_eq!(result2, None); /// ``` pub fn new(parser: P) -> Self { Self { parser } } } impl Parser for Optional

where P: Parser, { type Output = Option; fn id(&self) -> u64 { use std::any::TypeId; use std::hash::{DefaultHasher, Hash, Hasher}; let mut hasher = DefaultHasher::new(); TypeId::of::().hash(&mut hasher); self.parser.id().hash(&mut hasher); hasher.finish() } fn read( &self, source: &mut Source, cache: &mut impl ParsingCache, context: &mut Ctx, ) -> ParseResult where S: Parsable, { match self.parser.parse(source, cache, context) { Ok(result) => Ok(Some(result)), Err(Error::NoMatch) => Ok(None), Err(err) => Err(err), } } } // Convenience From trait for easier construction impl

From

for Optional

where P: Parser, { fn from(parser: P) -> Self { Self::new(parser) } } #[cfg(test)] mod tests { use super::*; use crate::{literal::Literal, parser::parse}; use std::io::Cursor; #[test] fn test_id_implementation_different_optional_parsers() { // Test that Optional implements proper id() method // Different Optional parsers should have different IDs to avoid cache conflicts let optional1 = Optional::new(Literal::from_str("hello")); let optional2 = Optional::new(Literal::from_str("world")); // These should have different IDs because they have different inner parsers // This test will FAIL if Optional uses default id() implementation assert_ne!( as crate::parser::Parser<()>>::id(&optional1), as crate::parser::Parser<()>>::id(&optional2), "Different Optional instances should have different IDs to avoid cache collisions" ); } #[test] fn test_id_implementation_same_optional_parsers() { // Test that identical Optional parsers have the same ID let optional1 = Optional::new(Literal::from_str("hello")); let optional2 = Optional::new(Literal::from_str("hello")); assert_eq!( as crate::parser::Parser<()>>::id(&optional1), as crate::parser::Parser<()>>::id(&optional2), "Identical Optional instances should have the same ID for cache efficiency" ); } #[test] fn test_optional_success() { let literal = Literal::from_str("hello"); let optional = Optional::new(literal); let mut input = Cursor::new(b"hello world"); let mut source = crate::parser::Source::new(&mut input); let result = parse(optional, &mut source).unwrap(); assert_eq!(result, Some(b"hello".as_slice().into())); } #[test] fn test_optional_none() { let literal = Literal::from_str("hello"); let optional = Optional::new(literal); let mut input = Cursor::new(b"world"); let mut source = crate::parser::Source::new(&mut input); let result = parse(optional, &mut source).unwrap(); assert_eq!(result, None); } #[test] fn test_optional_empty_input() { let literal = Literal::from_str("hello"); let optional = Optional::new(literal); let mut input = Cursor::new(b""); let mut source = crate::parser::Source::new(&mut input); let result = parse(optional, &mut source).unwrap(); assert_eq!(result, None); } #[test] fn test_optional_from_trait() { let literal = Literal::from_str("test"); let optional: Optional<_> = literal.into(); let mut input = Cursor::new(b"test"); let mut source = crate::parser::Source::new(&mut input); let result = parse(optional, &mut source).unwrap(); assert_eq!(result, Some(b"test".as_slice().into())); } }