//! 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, Option)` 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, Option)` 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 { first: A, second: B, } impl Either { /// 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>`. /// /// # 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 Parser for Either where A: Parser, B: Parser, { type Output = (Option, 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.first.id().hash(&mut hasher); self.second.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.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!( as crate::parser::Parser<()>>::id(&either1), 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!( as crate::parser::Parser<()>>::id(&either1), 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!( as crate::parser::Parser<()>>::id(&either1), 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, 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))); } }