use crate::{
cache::ParsingCache,
parser::{Parsable, Parser, Source},
result::{Error, ParseResult},
};
#[derive(Clone)]
pub struct Optional<P> {
parser: P,
}
impl<P> Optional<P> {
pub fn new(parser: P) -> Self {
Self { parser }
}
}
impl<P, Ctx> Parser<Ctx> for Optional<P>
where
P: Parser<Ctx>,
{
type Output = Option<P::Output>;
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);
self.parser.id().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,
{
match self.parser.parse(source, cache, context) {
Ok(result) => Ok(Some(result)),
Err(Error::NoMatch) => Ok(None),
Err(err) => Err(err),
}
}
}
impl<P> From<P> for Optional<P>
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() {
let optional1 = Optional::new(Literal::from_str("hello"));
let optional2 = Optional::new(Literal::from_str("world"));
assert_ne!(
<Optional<_> as crate::parser::Parser<()>>::id(&optional1),
<Optional<_> 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() {
let optional1 = Optional::new(Literal::from_str("hello"));
let optional2 = Optional::new(Literal::from_str("hello"));
assert_eq!(
<Optional<_> as crate::parser::Parser<()>>::id(&optional1),
<Optional<_> 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()));
}
}