either.rs
raw
//! Alternative parser combinator and the `oneof!` macro.
//!
//! This module provides the [`Either`] combinator for choosing between alternative
//! parsers, along with the [`oneof!`](crate::oneof) macro for convenient choice syntax.
//!
//! The Either combinator implements a Chain of Responsibility pattern, trying the
//! first parser and falling back to the second if the first fails with `NoMatch`.
//! The result is a tuple `(Option<A>, Option<B>)` where exactly one option contains
//! a value.
//!
//! Alternative composition is fundamental for building parsers that can handle
//! multiple possible input formats or syntax variations.
use crate::{
cache::ParsingCache,
parser::{Parsable, Parser, Source},
result::{Error, ParseResult},
};
/// A parser combinator that tries two parsers in sequence, succeeding if either matches.
///
/// Either applies the first parser, and if it fails with `NoMatch`, tries the second parser.
/// The output is a tuple `(Option<A::Output>, Option<B::Output>)` where exactly one option
/// contains the successful result and the other is `None`.
///
/// # Examples
///
/// ```rust
/// use neotoma::{either::Either, literal::Literal, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// // Match either "hello" or "world"
/// let greeting = Either::new(
/// Literal::from_str("hello"),
/// Literal::from_str("world")
/// );
///
/// // Matches: "hello" -> (Some("hello"), None)
/// let mut input1 = Cursor::new(b"hello");
/// let mut source1 = Source::new(input1);
/// let result1 = parse(greeting, &mut source1).unwrap();
/// assert_eq!(result1.0, Some(b"hello".as_slice().into()));
/// assert_eq!(result1.1, None);
///
/// // Matches: "world" -> (None, Some("world"))
/// let greeting2 = Either::new(
/// Literal::from_str("hello"),
/// Literal::from_str("world")
/// );
/// let mut input2 = Cursor::new(b"world");
/// let mut source2 = Source::new(input2);
/// let result2 = parse(greeting2, &mut source2).unwrap();
/// assert_eq!(result2.0, None);
/// assert_eq!(result2.1, Some(b"world".as_slice().into()));
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Either<A, B> {
first: A,
second: B,
}
impl<A, B> Either<A, B> {
/// Create a new Either parser that tries the first parser, then the second if the first fails.
///
/// The first parser is tried first. If it succeeds, returns `(Some(result), None)`.
/// If it fails with `NoMatch`, the second parser is tried. If the second parser succeeds,
/// returns `(None, Some(result))`. If both fail with `NoMatch`, Either fails with `NoMatch`.
/// Other errors (like IO errors) are propagated immediately.
///
/// # Examples
///
/// ```rust
/// use neotoma::{either::Either, literal::Literal, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let yes_or_no = Either::new(
/// Literal::from_str("yes"),
/// Literal::from_str("no")
/// );
///
/// // Matches "yes" -> (Some("yes"), None)
/// let mut input1 = Cursor::new(b"yes");
/// let mut source1 = Source::new(input1);
/// let result1 = parse(yes_or_no, &mut source1).unwrap();
/// assert_eq!(result1.0, Some(b"yes".as_slice().into()));
/// assert_eq!(result1.1, None);
///
/// // Matches "no" -> (None, Some("no"))
/// let yes_or_no2 = Either::new(
/// Literal::from_str("yes"),
/// Literal::from_str("no")
/// );
/// let mut input2 = Cursor::new(b"no");
/// let mut source2 = Source::new(input2);
/// let result2 = parse(yes_or_no2, &mut source2).unwrap();
/// assert_eq!(result2.0, None);
/// assert_eq!(result2.1, Some(b"no".as_slice().into()));
/// ```
pub fn new(first: A, second: B) -> Self {
Self { first, second }
}
}
/// Macro for creating Either parsers with multiple alternatives.
///
/// This macro creates a right-associative structure of Either parsers.
/// `oneof![A, B, C]` becomes `Either<A, Either<B, C>>`.
///
/// # Examples
///
/// ```rust
/// use neotoma::{oneof, literal::Literal, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// // Match one of several alternatives
/// let parser = oneof![
/// Literal::from_str("alpha"),
/// Literal::from_str("beta"),
/// Literal::from_str("gamma")
/// ];
///
/// // Test first alternative
/// let mut input = Cursor::new(b"alpha");
/// let mut source = Source::new(input);
/// let result = parse(parser, &mut source).unwrap();
/// assert_eq!(result.0, Some(b"alpha".as_slice().into()));
/// assert_eq!(result.1, None);
/// ```
#[macro_export]
macro_rules! oneof {
// Base case: single parser wrapped in Either with empty alternative
($parser:expr) => {
$parser
};
// Recursive case: first parser or oneof of the rest
($first:expr, $($rest:expr),+ $(,)?) => {
$crate::either::Either::new($first, oneof!($($rest),+))
};
}
impl<A, B, Ctx> Parser<Ctx> for Either<A, B>
where
A: Parser<Ctx>,
B: Parser<Ctx>,
{
type Output = (Option<A::Output>, Option<B::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.first.id().hash(&mut hasher);
self.second.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.first.parse(source, cache, context) {
Ok(result) => Ok((Some(result), None)),
Err(Error::NoMatch) => {
// First parser failed, try second
match self.second.parse(source, cache, context) {
Ok(result) => Ok((None, Some(result))),
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{literal::Literal, parser::parse};
use std::io::Cursor;
#[test]
fn test_id_implementation_different_either_parsers() {
// Test that Either implements proper id() method
// Different Either parsers should have different IDs to avoid cache conflicts
let either1 = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let either2 = Either::new(Literal::from_str("foo"), Literal::from_str("bar"));
// These should have different IDs because they have different inner parsers
// This test will FAIL if Either uses default id() implementation
assert_ne!(
<Either<_, _> as crate::parser::Parser<()>>::id(&either1),
<Either<_, _> as crate::parser::Parser<()>>::id(&either2),
"Different Either instances should have different IDs to avoid cache collisions"
);
}
#[test]
fn test_id_implementation_same_either_parsers() {
// Test that identical Either parsers have the same ID
let either1 = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let either2 = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
assert_eq!(
<Either<_, _> as crate::parser::Parser<()>>::id(&either1),
<Either<_, _> as crate::parser::Parser<()>>::id(&either2),
"Identical Either instances should have the same ID for cache efficiency"
);
}
#[test]
fn test_id_implementation_either_different_order() {
// Test that order matters in Either parsers
let either1 = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let either2 = Either::new(Literal::from_str("world"), Literal::from_str("hello"));
// These should have different IDs because order matters in Either
// This test will FAIL if Either uses default id() implementation
assert_ne!(
<Either<_, _> as crate::parser::Parser<()>>::id(&either1),
<Either<_, _> as crate::parser::Parser<()>>::id(&either2),
"Either instances with different order should have different IDs"
);
}
#[test]
fn test_either_first_matches() {
let parser = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let mut input = Cursor::new(b"hello");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source).unwrap();
assert_eq!(result.0, Some(b"hello".as_slice().into()));
assert_eq!(result.1, None);
}
#[test]
fn test_either_second_matches() {
let parser = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let mut input = Cursor::new(b"world");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source).unwrap();
assert_eq!(result.0, None);
assert_eq!(result.1, Some(b"world".as_slice().into()));
}
#[test]
fn test_either_neither_matches() {
let parser = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let mut input = Cursor::new(b"foo");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source);
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_either_empty_input() {
let parser = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let mut input = Cursor::new(b"");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source);
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_either_first_parser_priority() {
// Test that first parser has priority when both could match
let parser = Either::new(Literal::from_str("he"), Literal::from_str("hello"));
let mut input = Cursor::new(b"hello");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source).unwrap();
assert_eq!(result.0, Some(b"he".as_slice().into()));
assert_eq!(result.1, None);
}
#[test]
fn test_either_different_types() {
use crate::class::Class;
use crate::parser::parse;
// Either a literal or digits
let parser = Either::new(Literal::from_str("prefix"), Class::digits());
// First matches
let mut input1 = Cursor::new(b"prefix");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(parser, &mut source1).unwrap();
assert_eq!(result1.0, Some(b"prefix".as_slice().into()));
assert_eq!(result1.1, None);
// Second matches
let parser2 = Either::new(Literal::from_str("prefix"), Class::digits());
let mut input2 = Cursor::new(b"123");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(parser2, &mut source2).unwrap();
assert_eq!(result2.0, None);
assert_eq!(result2.1, Some(b"123".to_vec()));
}
#[test]
fn test_either_position_tracking() {
use crate::parser::parse;
// Verify position is correctly managed when first parser fails
let parser = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let mut input = Cursor::new(b"worldXYZ");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source).unwrap();
assert_eq!(result.0, None);
assert_eq!(result.1, Some(b"world".as_slice().into()));
// Position should be at 'X'
let next_byte = source.peek1().unwrap();
assert_eq!(next_byte, b'X');
}
#[test]
fn test_either_with_complex_parsers() {
use crate::{parser::parse, repeat::Repeat, sequence::Sequence};
// Either a repeated "a" or a sequence "hello world"
let repeat_a = Repeat::new(Literal::from_str("a"));
let hello_world = Sequence::new(Literal::from_str("hello"), Literal::from_str(" world"));
let parser = Either::new(repeat_a, hello_world);
// Test first alternative (repeated "a")
let mut input1 = Cursor::new(b"aaaXYZ");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(parser, &mut source1).unwrap();
if let Some(repeated_results) = result1.0 {
assert_eq!(repeated_results.len(), 3);
assert_eq!(result1.1, None);
} else {
// If first alternative didn't match, second should match
assert!(result1.1.is_some());
}
// Test second alternative (sequence)
let repeat_a2 = Repeat::new(Literal::from_str("a"));
let hello_world2 = Sequence::new(Literal::from_str("hello"), Literal::from_str(" world"));
let parser2 = Either::new(repeat_a2, hello_world2);
let mut input2 = Cursor::new(b"hello worldXYZ");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(parser2, &mut source2).unwrap();
// Either the repeat matched (possibly empty) or the sequence matched
if let Some(repeat_result) = result2.0 {
// If repeat matched, it should be empty for this input
assert_eq!(repeat_result.len(), 0);
} else if let Some(sequence_result) = result2.1 {
// If sequence matched, check its content
let (hello, world) = sequence_result;
assert_eq!(hello, b"hello".as_slice().into());
assert_eq!(world, b" world".as_slice().into());
} else {
panic!("Either should match one alternative");
}
}
#[test]
fn test_either_nested() {
use crate::parser::parse;
// Test nested Either parsers: Either<Either<A, B>, C>
let inner_either = Either::new(Literal::from_str("a"), Literal::from_str("b"));
let outer_either = Either::new(inner_either, Literal::from_str("c"));
// Test first inner alternative
let mut input1 = Cursor::new(b"a");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(outer_either, &mut source1).unwrap();
assert!(result1.0.is_some());
let inner_result = result1.0.unwrap();
assert_eq!(inner_result.0, Some(b"a".as_slice().into()));
assert_eq!(inner_result.1, None);
assert_eq!(result1.1, None);
// Test second inner alternative
let inner_either2 = Either::new(Literal::from_str("a"), Literal::from_str("b"));
let outer_either2 = Either::new(inner_either2, Literal::from_str("c"));
let mut input2 = Cursor::new(b"b");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(outer_either2, &mut source2).unwrap();
assert!(result2.0.is_some());
let inner_result2 = result2.0.unwrap();
assert_eq!(inner_result2.0, None);
assert_eq!(inner_result2.1, Some(b"b".as_slice().into()));
assert_eq!(result2.1, None);
// Test outer alternative
let inner_either3 = Either::new(Literal::from_str("a"), Literal::from_str("b"));
let outer_either3 = Either::new(inner_either3, Literal::from_str("c"));
let mut input3 = Cursor::new(b"c");
let mut source3 = crate::parser::Source::new(&mut input3);
let result3 = parse(outer_either3, &mut source3).unwrap();
assert_eq!(result3.0, None);
assert_eq!(result3.1, Some(b"c".as_slice().into()));
}
#[test]
fn test_either_chained_alternatives() {
use crate::parser::parse;
// Test chaining multiple Either parsers for many alternatives
// Test each alternative
let test_cases = vec![
("alpha", "first"),
("beta", "second"),
("gamma", "third"),
("delta", "fourth"),
];
for (input_str, description) in test_cases {
let choice1 = Either::new(Literal::from_str("alpha"), Literal::from_str("beta"));
let choice2 = Either::new(choice1, Literal::from_str("gamma"));
let choice3 = Either::new(choice2, Literal::from_str("delta"));
let mut input = Cursor::new(input_str.as_bytes());
let mut source = crate::parser::Source::new(&mut input);
let result = parse(choice3, &mut source);
assert!(result.is_ok(), "Failed to parse {description} alternative",);
}
}
#[test]
fn test_either_with_bounds() {
use crate::{class::Class, parser::parse};
// Either exactly 2 digits or exactly 3 letters
let two_digits = Class::with_bounds(b"0123456789", 2, 2);
let three_letters = Class::with_bounds(b"abcdefghijklmnopqrstuvwxyz", 3, 3);
let parser = Either::new(two_digits, three_letters);
// Test two digits
let mut input1 = Cursor::new(b"12X");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(parser, &mut source1).unwrap();
assert_eq!(result1.0, Some(b"12".to_vec()));
assert_eq!(result1.1, None);
// Test three letters
let two_digits2 = Class::with_bounds(b"0123456789", 2, 2);
let three_letters2 = Class::with_bounds(b"abcdefghijklmnopqrstuvwxyz", 3, 3);
let parser2 = Either::new(two_digits2, three_letters2);
let mut input2 = Cursor::new(b"abc1");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(parser2, &mut source2).unwrap();
assert_eq!(result2.0, None);
assert_eq!(result2.1, Some(b"abc".to_vec()));
}
#[test]
fn test_either_failure_modes() {
use crate::parser::parse;
// Test when both alternatives fail
let parser = Either::new(Literal::from_str("hello"), Literal::from_str("world"));
let mut input = Cursor::new(b"goodbye");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source);
assert!(matches!(result, Err(Error::NoMatch)));
// Verify position wasn't advanced
let first_byte = source.peek1().unwrap();
assert_eq!(first_byte, b'g');
}
#[test]
fn test_either_backtracking_behavior() {
use crate::{class::Class, parser::parse};
// First parser consumes some input then fails, second should still work
let digits_then_alpha = crate::sequence::Sequence::new(Class::digits(), Class::alpha());
let just_alpha = Class::alpha();
let parser = Either::new(digits_then_alpha, just_alpha);
// Input has only letters, first parser should fail after consuming digits
let mut input = Cursor::new(b"abcdef");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source).unwrap();
assert_eq!(result.0, None);
assert_eq!(result.1, Some(b"abcdef".to_vec()));
}
#[test]
fn test_either_cache_interaction() {
use crate::parser::parse;
// Test that Either works correctly with caching
let parser1 = Either::new(Literal::from_str("test"), Literal::from_str("demo"));
let parser2 = Either::new(Literal::from_str("test"), Literal::from_str("demo"));
// Parse first time
let mut input1 = Cursor::new(b"testXYZ");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(parser1, &mut source1).unwrap();
assert_eq!(result1.0, Some(b"test".as_slice().into()));
// Parse with different input at different position - should work correctly
let mut input2 = Cursor::new(b"demoXYZ");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(parser2, &mut source2).unwrap();
// Either parser can match either alternative, test that we get a valid result
assert!(result2.0.is_some() || result2.1.is_some());
}
#[test]
fn test_oneof_macro_single() {
use crate::{oneof, parser::parse};
let parser = oneof![Literal::from_str("hello")];
let mut input = Cursor::new(b"hello");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_oneof_macro_two() {
use crate::{oneof, parser::parse};
let parser = oneof![Literal::from_str("hello"), Literal::from_str("world")];
// Test first alternative
let mut input1 = Cursor::new(b"hello");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(parser, &mut source1).unwrap();
assert_eq!(result1.0, Some(b"hello".as_slice().into()));
assert_eq!(result1.1, None);
// Test second alternative
let parser2 = oneof![Literal::from_str("hello"), Literal::from_str("world")];
let mut input2 = Cursor::new(b"world");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(parser2, &mut source2).unwrap();
assert_eq!(result2.0, None);
assert_eq!(result2.1, Some(b"world".as_slice().into()));
}
#[test]
fn test_oneof_macro_three() {
use crate::{oneof, parser::parse};
let parser = oneof![
Literal::from_str("alpha"),
Literal::from_str("beta"),
Literal::from_str("gamma")
];
// Test first alternative
let mut input1 = Cursor::new(b"alpha");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(parser, &mut source1).unwrap();
assert_eq!(result1.0, Some(b"alpha".as_slice().into()));
assert_eq!(result1.1, None);
// Test second alternative (nested in right side)
let parser2 = oneof![
Literal::from_str("alpha"),
Literal::from_str("beta"),
Literal::from_str("gamma")
];
let mut input2 = Cursor::new(b"beta");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(parser2, &mut source2).unwrap();
assert_eq!(result2.0, None);
assert!(result2.1.is_some());
if let Some(inner_result) = result2.1 {
assert_eq!(inner_result.0, Some(b"beta".as_slice().into()));
assert_eq!(inner_result.1, None);
}
// Test third alternative
let parser3 = oneof![
Literal::from_str("alpha"),
Literal::from_str("beta"),
Literal::from_str("gamma")
];
let mut input3 = Cursor::new(b"gamma");
let mut source3 = crate::parser::Source::new(&mut input3);
let result3 = parse(parser3, &mut source3).unwrap();
assert_eq!(result3.0, None);
assert!(result3.1.is_some());
if let Some(inner_result) = result3.1 {
assert_eq!(inner_result.0, None);
assert_eq!(inner_result.1, Some(b"gamma".as_slice().into()));
}
}
#[test]
fn test_oneof_macro_four() {
use crate::{oneof, parser::parse};
// Test each alternative
let test_cases = vec![
("alpha", "first"),
("beta", "second"),
("gamma", "third"),
("delta", "fourth"),
];
for (input_str, _description) in test_cases {
let parser = oneof![
Literal::from_str("alpha"),
Literal::from_str("beta"),
Literal::from_str("gamma"),
Literal::from_str("delta")
];
let mut input = Cursor::new(input_str.as_bytes());
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source);
assert!(result.is_ok());
}
}
#[test]
fn test_oneof_macro_with_trailing_comma() {
use crate::{oneof, parser::parse};
let parser = oneof![Literal::from_str("hello"), Literal::from_str("world"),];
let mut input = Cursor::new(b"hello");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source).unwrap();
assert_eq!(result.0, Some(b"hello".as_slice().into()));
assert_eq!(result.1, None);
}
#[test]
fn test_oneof_macro_different_types() {
use crate::{class::Class, oneof, parser::parse};
let parser = oneof![Literal::from_str("prefix"), Class::digits(), Class::alpha()];
// Test literal
let mut input1 = Cursor::new(b"prefix");
let mut source1 = crate::parser::Source::new(&mut input1);
let result1 = parse(parser, &mut source1).unwrap();
assert_eq!(result1.0, Some(b"prefix".as_slice().into()));
assert_eq!(result1.1, None);
// Test digits
let parser2 = oneof![Literal::from_str("prefix"), Class::digits(), Class::alpha()];
let mut input2 = Cursor::new(b"123");
let mut source2 = crate::parser::Source::new(&mut input2);
let result2 = parse(parser2, &mut source2).unwrap();
assert_eq!(result2.0, None);
assert!(result2.1.is_some());
if let Some(inner_result) = result2.1 {
assert_eq!(inner_result.0, Some(b"123".to_vec()));
assert_eq!(inner_result.1, None);
}
// Test alpha
let parser3 = oneof![Literal::from_str("prefix"), Class::digits(), Class::alpha()];
let mut input3 = Cursor::new(b"abc");
let mut source3 = crate::parser::Source::new(&mut input3);
let result3 = parse(parser3, &mut source3).unwrap();
assert_eq!(result3.0, None);
assert!(result3.1.is_some());
if let Some(inner_result) = result3.1 {
assert_eq!(inner_result.0, None);
assert_eq!(inner_result.1, Some(b"abc".to_vec()));
}
}
#[test]
fn test_oneof_macro_failure() {
use crate::{oneof, parser::parse};
let parser = oneof![
Literal::from_str("hello"),
Literal::from_str("world"),
Literal::from_str("test")
];
let mut input = Cursor::new(b"goodbye");
let mut source = crate::parser::Source::new(&mut input);
let result = parse(parser, &mut source);
assert!(matches!(result, Err(crate::result::Error::NoMatch)));
}
}