sequence.rs
raw
//! 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<A, Sequence<B, Sequence<C, ()>>>`.
//! 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<A, Sequence<B, Sequence<C, ()>>>` 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<P, ()>
($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<A, B> {
first: A,
second: B,
}
/// Trait for types that can have a parser pushed to their end.
pub trait Push<P> {
type Output;
fn push(self, parser: P) -> Self::Output;
}
impl<A, B> Sequence<A, B> {
/// 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<A, B> Sequence<A, B> {
/// 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<P>(self, parser: P) -> Sequence<A, B::Output>
where
B: Push<P>,
{
Sequence::new(self.first, self.second.push(parser))
}
}
// Base case: () can be pushed to, becoming Sequence<P, ()>
impl<P> Push<P> for () {
type Output = Sequence<P, ()>;
fn push(self, parser: P) -> Self::Output {
Sequence::new(parser, ())
}
}
// Recursive case: Sequence<A, B> can be pushed to if B can be pushed to
impl<A, B, P> Push<P> for Sequence<A, B>
where
B: Push<P>,
{
type Output = Sequence<A, B::Output>;
fn push(self, parser: P) -> Self::Output {
Sequence::new(self.first, self.second.push(parser))
}
}
impl<A, B, Ctx> Parser<Ctx> for Sequence<A, B>
where
A: Parser<Ctx>,
B: Parser<Ctx>,
{
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::<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,
{
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 = <Sequence<_, _> as crate::parser::Parser<()>>::id(&seq1);
let id2 = <Sequence<_, _> 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!(
<Sequence<_, _> as crate::parser::Parser<()>>::id(&seq1),
<Sequence<_, _> 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 = <Sequence<_, _> as crate::parser::Parser<()>>::id(&seq1);
let id2 = <Sequence<_, _> as crate::parser::Parser<()>>::id(&seq2);
assert_ne!(
id1, id2,
"Different nested Sequence instances should have different IDs to avoid cache collisions"
);
}
}