//! Sequential parser composition and the `seq!` macro. //! //! This module provides the [`Sequence`] combinator for chaining parsers together //! in order, along with the [`seq!`](crate::seq) macro for convenient composition syntax. //! //! The implementation uses a Lisp-style right-associative nesting pattern where //! `seq![A, B, C]` expands to `Sequence>>`. //! This creates nested tuple outputs following the same pattern. //! //! Sequential composition is fundamental to building structured parsers that //! match multiple elements in a specific order. use crate::{ cache::ParsingCache, parser::{Parsable, Parser, Source}, result::ParseResult, }; /// Create a sequence parser from multiple parsers. /// /// This macro creates a Lisp-style nested list structure from the /// provided parsers. The resulting type follows the pattern: /// `Sequence>>` for `seq![A, B, C]`. /// /// # Examples /// /// ```rust /// use neotoma::{seq, literal::Literal, parser::{parse, Source}}; /// use std::io::Cursor; /// /// // Create a sequence of three literal parsers /// let greeting = seq![ /// Literal::from_str("hello"), /// Literal::from_str(" "), /// Literal::from_str("world") /// ]; /// /// let mut input = Cursor::new(b"hello world"); /// let mut source = Source::new(input); /// let result = parse(greeting, &mut source).unwrap(); /// assert_eq!(result.0, b"hello".as_slice().into()); /// assert_eq!(result.1.0, b" ".as_slice().into()); /// assert_eq!(result.1.1.0, b"world".as_slice().into()); /// assert_eq!(result.1.1.1, ()); /// ``` /// /// The macro supports sequences of any length: /// /// ```rust /// use neotoma::{seq, literal::Literal}; /// /// // Two parsers /// let two = seq![Literal::from_str("a"), Literal::from_str("b")]; /// /// // Five parsers /// let five = seq![ /// Literal::from_str("a"), /// Literal::from_str("b"), /// Literal::from_str("c"), /// Literal::from_str("d"), /// Literal::from_str("e") /// ]; /// ``` #[macro_export] macro_rules! seq { // Base case: single parser becomes Sequence ($parser:expr) => { $crate::sequence::Sequence::new($parser, ()) }; // Recursive case: first parser + sequence of rest ($first:expr, $($rest:expr),+ $(,)?) => { $crate::sequence::Sequence::new($first, seq!($($rest),+)) }; } /// A parser combinator that matches two parsers in sequence. /// /// Sequence applies the first parser, and if it succeeds, applies the second parser. /// The output is a tuple `(A::Output, B::Output)` containing both results. /// If either parser fails, the entire sequence fails. /// /// # Examples /// /// ```rust /// use neotoma::{sequence::Sequence, literal::Literal, parser::{parse, Source}}; /// use std::io::Cursor; /// /// // Match "hello" followed by "world" /// let greeting = Sequence::new( /// Literal::from_str("hello"), /// Literal::from_str("world") /// ); /// /// let mut input = Cursor::new(b"helloworld"); /// let mut source = Source::new(input); /// let result = parse(greeting, &mut source).unwrap(); /// assert_eq!(result.0, b"hello".as_slice().into()); /// assert_eq!(result.1, b"world".as_slice().into()); /// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub struct Sequence { first: A, second: B, } /// Trait for types that can have a parser pushed to their end. pub trait Push

{ type Output; fn push(self, parser: P) -> Self::Output; } impl Sequence { /// Create a new Sequence parser that matches the first parser followed by the second. /// /// Both parsers must succeed for the sequence to succeed. The output is a tuple /// containing both results. /// /// For sequences of more than two parsers, consider using the `seq!` macro instead. /// /// # Examples /// /// ```rust /// use neotoma::{sequence::Sequence, literal::Literal, parser::{parse, Source}}; /// use std::io::Cursor; /// /// let greeting = Sequence::new( /// Literal::from_str("hello"), /// Literal::from_str(" world") /// ); /// /// let mut input = Cursor::new(b"hello world"); /// let mut source = Source::new(input); /// let result = parse(greeting, &mut source).unwrap(); /// assert_eq!(result.0, b"hello".as_slice().into()); /// assert_eq!(result.1, b" world".as_slice().into()); /// ``` pub fn new(first: A, second: B) -> Self { Self { first, second } } } impl Sequence { /// Push a new parser to the end of this sequence. /// /// This extends the sequence by appending a new parser to the end of the /// right-associative nesting structure. /// /// # Examples /// /// ```rust /// use neotoma::{seq, sequence::Push, literal::Literal, parser::{parse, Source}}; /// use std::io::Cursor; /// /// let base = seq![Literal::from_str("hello"), Literal::from_str(" ")]; /// let extended = base.push(Literal::from_str("world")); /// /// let mut input = Cursor::new(b"hello world"); /// let mut source = Source::new(input); /// let result = parse(extended, &mut source).unwrap(); /// assert_eq!(result.0, b"hello".as_slice().into()); /// assert_eq!(result.1.0, b" ".as_slice().into()); /// assert_eq!(result.1.1.0, b"world".as_slice().into()); /// assert_eq!(result.1.1.1, ()); /// ``` pub fn push

(self, parser: P) -> Sequence where B: Push

, { Sequence::new(self.first, self.second.push(parser)) } } // Base case: () can be pushed to, becoming Sequence impl

Push

for () { type Output = Sequence; fn push(self, parser: P) -> Self::Output { Sequence::new(parser, ()) } } // Recursive case: Sequence can be pushed to if B can be pushed to impl Push

for Sequence where B: Push

, { type Output = Sequence; fn push(self, parser: P) -> Self::Output { Sequence::new(self.first, self.second.push(parser)) } } impl Parser for Sequence where A: Parser, B: Parser, { type Output = (A::Output, B::Output); 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, { let first_result = self.first.parse(source, cache, context)?; let second_result = self.second.parse(source, cache, context)?; Ok((first_result, second_result)) } } #[cfg(test)] mod tests { use super::*; use crate::{literal::Literal, parser::parse, seq}; use std::io::Cursor; #[test] fn test_sequence_both_match() { let parser = Sequence::new(Literal::from_str("hello"), Literal::from_str("world")); let mut input = Cursor::new(b"helloworld"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1, b"world".as_slice().into()); } #[test] fn test_sequence_first_fails() { let parser = Sequence::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!(result.is_err()); } #[test] fn test_sequence_second_fails() { let parser = Sequence::new(Literal::from_str("hello"), Literal::from_str("world")); let mut input = Cursor::new(b"hellogoodbye"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source); assert!(result.is_err()); } #[test] fn test_sequence_empty_input() { let parser = Sequence::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!(result.is_err()); } #[test] fn test_seq_macro_single() { let parser = seq![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, b"hello".as_slice().into()); } #[test] fn test_seq_macro_two() { let parser = seq![Literal::from_str("hello"), Literal::from_str("world")]; let mut input = Cursor::new(b"helloworld"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1.0, b"world".as_slice().into()); } #[test] fn test_seq_macro_three() { let parser = seq![ Literal::from_str("hello"), Literal::from_str(" "), Literal::from_str("world") ]; let mut input = Cursor::new(b"hello world"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1.0, b" ".as_slice().into()); assert_eq!(result.1.1.0, b"world".as_slice().into()); } #[test] fn test_seq_macro_four() { let parser = seq![ Literal::from_str("hello"), Literal::from_str(" "), Literal::from_str("beautiful"), Literal::from_str(" world") ]; let mut input = Cursor::new(b"hello beautiful world"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1.0, b" ".as_slice().into()); assert_eq!(result.1.1.0, b"beautiful".as_slice().into()); assert_eq!(result.1.1.1.0, b" world".as_slice().into()); } #[test] fn test_seq_macro_five() { let parser = seq![ Literal::from_str("a"), Literal::from_str("b"), Literal::from_str("c"), Literal::from_str("d"), Literal::from_str("e") ]; let mut input = Cursor::new(b"abcde"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"a".as_slice().into()); assert_eq!(result.1.0, b"b".as_slice().into()); assert_eq!(result.1.1.0, b"c".as_slice().into()); assert_eq!(result.1.1.1.0, b"d".as_slice().into()); assert_eq!(result.1.1.1.1.0, b"e".as_slice().into()); } #[test] fn test_seq_macro_with_trailing_comma() { let parser = seq![ Literal::from_str("hello"), Literal::from_str(" "), Literal::from_str("world"), ]; let mut input = Cursor::new(b"hello world"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1.0, b" ".as_slice().into()); assert_eq!(result.1.1.0, b"world".as_slice().into()); } #[test] fn test_sequence_different_types() { use crate::class::Class; let parser = Sequence::new(Literal::from_str("prefix"), Class::digits()); let mut input = Cursor::new(b"prefix123"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"prefix".as_slice().into()); assert_eq!(result.1, b"123".to_vec()); } #[test] fn test_seq_macro_different_types() { use crate::class::Class; let parser = seq![ Literal::from_str("prefix"), Class::digits(), Literal::from_str("suffix") ]; let mut input = Cursor::new(b"prefix123suffix"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"prefix".as_slice().into()); assert_eq!(result.1.0, b"123".to_vec()); assert_eq!(result.1.1.0, b"suffix".as_slice().into()); } #[test] fn test_sequence_position_tracking() { let parser = Sequence::new(Literal::from_str("hello"), Literal::from_str("world")); let mut input = Cursor::new(b"helloworld123"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1, b"world".as_slice().into()); // Position should be advanced past both parsers - test indirectly // by verifying we can read the remaining bytes let remaining = source.peek1().unwrap(); assert_eq!(remaining, b'1'); } #[test] fn test_seq_macro_failure() { let parser = seq![ Literal::from_str("hello"), Literal::from_str(" "), Literal::from_str("world") ]; let mut input = Cursor::new(b"hello goodbye"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source); assert!(result.is_err()); } #[test] fn test_push_to_single_element() { let base = seq![Literal::from_str("hello")]; let extended = base.push(Literal::from_str(" world")); let mut input = Cursor::new(b"hello world"); let mut source = crate::parser::Source::new(&mut input); let result = parse(extended, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1.0, b" world".as_slice().into()); } #[test] fn test_push_to_two_elements() { let base = seq![Literal::from_str("hello"), Literal::from_str(" ")]; let extended = base.push(Literal::from_str("world")); let mut input = Cursor::new(b"hello world"); let mut source = crate::parser::Source::new(&mut input); let result = parse(extended, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1.0, b" ".as_slice().into()); assert_eq!(result.1.1.0, b"world".as_slice().into()); } #[test] fn test_push_to_three_elements() { let base = seq![ Literal::from_str("hello"), Literal::from_str(" "), Literal::from_str("beautiful") ]; let extended = base.push(Literal::from_str(" world")); let mut input = Cursor::new(b"hello beautiful world"); let mut source = crate::parser::Source::new(&mut input); let result = parse(extended, &mut source).unwrap(); assert_eq!(result.0, b"hello".as_slice().into()); assert_eq!(result.1.0, b" ".as_slice().into()); assert_eq!(result.1.1.0, b"beautiful".as_slice().into()); assert_eq!(result.1.1.1.0, b" world".as_slice().into()); } #[test] fn test_push_multiple_times() { let base = seq![Literal::from_str("a")]; let step1 = base.push(Literal::from_str("b")); let step2 = step1.push(Literal::from_str("c")); let final_parser = step2.push(Literal::from_str("d")); let mut input = Cursor::new(b"abcd"); let mut source = crate::parser::Source::new(&mut input); let result = parse(final_parser, &mut source).unwrap(); assert_eq!(result.0, b"a".as_slice().into()); assert_eq!(result.1.0, b"b".as_slice().into()); assert_eq!(result.1.1.0, b"c".as_slice().into()); assert_eq!(result.1.1.1.0, b"d".as_slice().into()); } #[test] fn test_push_chaining() { let parser = seq![Literal::from_str("a")] .push(Literal::from_str("b")) .push(Literal::from_str("c")) .push(Literal::from_str("d")); let mut input = Cursor::new(b"abcd"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"a".as_slice().into()); assert_eq!(result.1.0, b"b".as_slice().into()); assert_eq!(result.1.1.0, b"c".as_slice().into()); assert_eq!(result.1.1.1.0, b"d".as_slice().into()); } #[test] fn test_push_different_types() { use crate::class::Class; let base = seq![Literal::from_str("prefix")]; let extended = base.push(Class::digits()); let mut input = Cursor::new(b"prefix123"); let mut source = crate::parser::Source::new(&mut input); let result = parse(extended, &mut source).unwrap(); assert_eq!(result.0, b"prefix".as_slice().into()); assert_eq!(result.1.0, b"123".to_vec()); } #[test] fn test_push_mixed_types_chain() { use crate::class::Class; let parser = seq![Literal::from_str("start")] .push(Class::digits()) .push(Literal::from_str("_")) .push(Class::digits()) // Use digits instead of alpha for simplicity .push(Literal::from_str("end")); let mut input = Cursor::new(b"start123_456end"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"start".as_slice().into()); assert_eq!(result.1.0, b"123".to_vec()); assert_eq!(result.1.1.0, b"_".as_slice().into()); assert_eq!(result.1.1.1.0, b"456".to_vec()); assert_eq!(result.1.1.1.1.0, b"end".as_slice().into()); } #[test] fn test_push_failure() { let base = seq![Literal::from_str("hello")]; let extended = base.push(Literal::from_str(" world")); let mut input = Cursor::new(b"hello goodbye"); let mut source = crate::parser::Source::new(&mut input); let result = parse(extended, &mut source); assert!(result.is_err()); } #[test] fn test_push_with_class_alpha() { use crate::class::Class; // Important: Class::alpha() consumes ALL consecutive alphabetic chars greedily // So we need a non-alphabetic separator to stop it from consuming everything let parser = seq![Literal::from_str("start")] .push(Class::digits()) .push(Literal::from_str("_")) .push(Class::alpha()) .push(Literal::from_str("_end")); let mut input = Cursor::new(b"start123_abc_end"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"start".as_slice().into()); assert_eq!(result.1.0, b"123".to_vec()); assert_eq!(result.1.1.0, b"_".as_slice().into()); assert_eq!(result.1.1.1.0, b"abc".to_vec()); assert_eq!(result.1.1.1.1.0, b"_end".as_slice().into()); } #[test] fn test_push_with_until_parser() { use crate::{class::Class, until::Until}; // Better solution using Until parser - no need for workarounds! let parser = seq![Literal::from_str("start")] .push(Class::digits()) .push(Literal::from_str("_")) .push(Until::new(Literal::from_str("end"))) .push(Literal::from_str("end")); let mut input = Cursor::new(b"start123_abcend"); let mut source = crate::parser::Source::new(&mut input); let result = parse(parser, &mut source).unwrap(); assert_eq!(result.0, b"start".as_slice().into()); assert_eq!(result.1.0, b"123".to_vec()); assert_eq!(result.1.1.0, b"_".as_slice().into()); assert_eq!(result.1.1.1.0, b"abc".to_vec()); // Until stops at "end" assert_eq!(result.1.1.1.1.0, b"end".as_slice().into()); } #[test] fn test_id_implementation_different_sequences() { // This test checks that Sequence implements proper id() method // Sequences with different parameters should have different IDs to avoid cache conflicts let seq1 = Sequence::new(Literal::from_str("hello"), Literal::from_str("world")); let seq2 = Sequence::new(Literal::from_str("foo"), Literal::from_str("bar")); // These sequences have different content and should have different IDs // This test will FAIL if Sequence uses default id() implementation let id1 = as crate::parser::Parser<()>>::id(&seq1); let id2 = as crate::parser::Parser<()>>::id(&seq2); assert_ne!( id1, id2, "Different Sequence instances should have different IDs to avoid cache collisions" ); } #[test] fn test_id_implementation_same_sequences() { // Test that identical sequences have the same ID let seq1 = Sequence::new(Literal::from_str("hello"), Literal::from_str("world")); let seq2 = Sequence::new(Literal::from_str("hello"), Literal::from_str("world")); assert_eq!( as crate::parser::Parser<()>>::id(&seq1), as crate::parser::Parser<()>>::id(&seq2), "Identical Sequence instances should have the same ID for cache efficiency" ); } #[test] fn test_id_implementation_sequence_cache_correctness() { // This test verifies that cache works correctly without collisions // when Sequence implements proper id() method let seq1 = Sequence::new(Literal::from_str("hello"), Literal::from_str("world")); let seq2 = Sequence::new(Literal::from_str("foo"), Literal::from_str("bar")); // Parse with first sequence let mut input1 = Cursor::new(b"helloworld"); let mut source1 = crate::parser::Source::new(&mut input1); let result1 = parse(seq1, &mut source1); assert!(result1.is_ok(), "First parse should succeed"); // Parse with second sequence at same position (0) // This should work correctly without cache collision let mut input2 = Cursor::new(b"foobar"); let mut source2 = crate::parser::Source::new(&mut input2); let result2 = parse(seq2, &mut source2); assert!( result2.is_ok(), "Second parse should succeed without cache collision" ); // Verify results are correct (no cache collision occurred) if let (Ok((first1, second1)), Ok((first2, second2))) = (result1, result2) { assert_eq!(first1, b"hello".as_slice().into()); assert_eq!(second1, b"world".as_slice().into()); assert_eq!(first2, b"foo".as_slice().into()); assert_eq!(second2, b"bar".as_slice().into()); } else { panic!("Both parses should succeed"); } } #[test] fn test_id_implementation_nested_sequences() { // Test nested sequences have proper ID differentiation let seq1 = Sequence::new( Literal::from_str("outer1"), Sequence::new(Literal::from_str("inner1"), Literal::from_str("end1")), ); let seq2 = Sequence::new( Literal::from_str("outer2"), Sequence::new(Literal::from_str("inner2"), Literal::from_str("end2")), ); // Nested sequences should have different IDs // This test will FAIL if nested sequences use default id() implementation let id1 = as crate::parser::Parser<()>>::id(&seq1); let id2 = as crate::parser::Parser<()>>::id(&seq2); assert_ne!( id1, id2, "Different nested Sequence instances should have different IDs to avoid cache collisions" ); } }