repeat.rs raw

//! Repetition parser combinator for matching repeated patterns.
//!
//! This module provides the [`Repeat`] parser combinator that applies another parser
//! multiple times, collecting all successful results into a `Vec`. It supports
//! configurable minimum and maximum repetition counts, as well as separator-based
//! repetition for parsing lists with delimiters.
//!
//! Repeat parsers are essential for parsing arrays, lists, and other variable-length
//! structures in input data. The module includes safeguards against infinite loops
//! when parsing empty matches.

use crate::{
    cache::ParsingCache,
    parser::{Parsable, Parser, Source},
    result::{Error, ParseResult},
};

/// A parser combinator that repeats another parser a specified number of times.
///
/// Repeat applies a contained parser repeatedly until it fails, collecting all
/// successful results into a `Vec`. You can specify minimum and maximum repetition
/// counts to control the matching behavior.
///
/// # Examples
///
/// ```rust
/// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
/// use std::io::Cursor;
///
/// let digit_parser = Literal::from_str("1");
///
/// // Repeat 0 or more times (default)
/// let parser = Repeat::new(digit_parser);
/// let mut input1 = Cursor::new(b"111abc");
/// let mut source1 = Source::new(input1);
/// let result1 = parse(parser, &mut source1).unwrap();
/// assert_eq!(result1.len(), 3);
///
/// // Repeat at least 3 times
/// let digit_parser2 = Literal::from_str("1");
/// let parser2 = Repeat::with_min(digit_parser2, 3);
/// let mut input2 = Cursor::new(b"1111abc");
/// let mut source2 = Source::new(input2);
/// let result2 = parse(parser2, &mut source2).unwrap();
/// assert_eq!(result2.len(), 4);
///
/// // Repeat at most 5 times
/// let digit_parser3 = Literal::from_str("1");
/// let parser3 = Repeat::with_max(digit_parser3, 5);
/// let mut input3 = Cursor::new(b"11111111abc");
/// let mut source3 = Source::new(input3);
/// let result3 = parse(parser3, &mut source3).unwrap();
/// assert_eq!(result3.len(), 5); // stops at 5
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Repeat<P, J = ()> {
    parser: P,
    min: usize,
    max: Option<usize>,
    joint: Option<J>,
}

impl<P> Repeat<P, ()> {
    /// Create a new Repeat parser with 0 minimum and no maximum repetitions.
    ///
    /// This will match the contained parser 0 or more times until it fails.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let digit_parser = Literal::from_str("1");
    /// let parser = Repeat::new(digit_parser);
    ///
    /// // Matches: "", "1", "123", "999999", etc.
    /// let mut input1 = Cursor::new(b"111abc");
    /// let mut source1 = Source::new(input1);
    /// let result1 = parse(parser, &mut source1).unwrap();
    /// assert_eq!(result1.len(), 3);
    ///
    /// // Matches empty on non-matching input
    /// let digit_parser2 = Literal::from_str("1");
    /// let parser2 = Repeat::new(digit_parser2);
    /// let mut input2 = Cursor::new(b"abc");
    /// let mut source2 = Source::new(input2);
    /// let result2 = parse(parser2, &mut source2).unwrap();
    /// assert_eq!(result2.len(), 0);
    /// ```
    pub fn new(parser: P) -> Self {
        Self {
            parser,
            min: 0,
            max: None,
            joint: None,
        }
    }

    /// Create a new Repeat parser with a minimum number of repetitions.
    ///
    /// The parser must succeed at least `min` times or the entire parse fails.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let digit_parser = Literal::from_str("1");
    /// let parser = Repeat::with_min(digit_parser, 2);
    ///
    /// // Matches: "11", "111", "1111", etc.
    /// let mut input1 = Cursor::new(b"111abc");
    /// let mut source1 = Source::new(input1);
    /// let result1 = parse(parser, &mut source1).unwrap();
    /// assert_eq!(result1.len(), 3);
    ///
    /// // Fails on: "", "1"
    /// let digit_parser2 = Literal::from_str("1");
    /// let parser2 = Repeat::with_min(digit_parser2, 2);
    /// let mut input2 = Cursor::new(b"1abc");
    /// let mut source2 = Source::new(input2);
    /// let result2 = parse(parser2, &mut source2);
    /// assert!(result2.is_err()); // fails because only 1 match
    /// ```
    pub fn with_min(parser: P, min: usize) -> Self {
        Self {
            parser,
            min,
            max: None,
            joint: None,
        }
    }

    /// Create a new Repeat parser with a maximum number of repetitions.
    ///
    /// The parser will stop after `max` successful matches, even if more
    /// matches are possible.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let digit_parser = Literal::from_str("1");
    /// let parser = Repeat::with_max(digit_parser, 3);
    ///
    /// // From "11111", matches "111" and stops
    /// let mut input = Cursor::new(b"11111abc");
    /// let mut source = Source::new(input);
    /// let result = parse(parser, &mut source).unwrap();
    /// assert_eq!(result.len(), 3); // stops at 3
    /// ```
    pub fn with_max(parser: P, max: usize) -> Self {
        Self {
            parser,
            min: 0,
            max: Some(max),
            joint: None,
        }
    }

    /// Create a new Repeat parser with both minimum and maximum repetitions.
    ///
    /// The parser must succeed at least `min` times and will stop after
    /// `max` times, even if more matches are possible.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let digit_parser = Literal::from_str("1");
    /// let parser = Repeat::with_bounds(digit_parser, 2, 4);
    ///
    /// // Matches 2-4 digits: "11", "111", "1111"
    /// let mut input1 = Cursor::new(b"111abc");
    /// let mut source1 = Source::new(input1);
    /// let result1 = parse(parser, &mut source1).unwrap();
    /// assert_eq!(result1.len(), 3);
    ///
    /// // Stops at 4 even from "111111"
    /// let digit_parser2 = Literal::from_str("1");
    /// let parser2 = Repeat::with_bounds(digit_parser2, 2, 4);
    /// let mut input2 = Cursor::new(b"111111abc");
    /// let mut source2 = Source::new(input2);
    /// let result2 = parse(parser2, &mut source2).unwrap();
    /// assert_eq!(result2.len(), 4); // stops at 4
    /// ```
    pub fn with_bounds(parser: P, min: usize, max: usize) -> Self {
        Self {
            parser,
            min,
            max: Some(max),
            joint: None,
        }
    }
}

impl<P, J> Repeat<P, J> {
    /// Create a new Repeat parser with a joint parser.
    ///
    /// The joint parser will be matched between each instance of the main parser,
    /// discarding the match results but not ignoring errors. The joint parser
    /// may also match at the end of the list but is not required to.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let digit_parser = Literal::from_str("1");
    /// let comma_parser = Literal::from_str(",");
    ///
    /// // Parse comma-separated values: "1,1,1" or "1,1,1,"
    /// let parser = Repeat::with_joint(digit_parser, comma_parser);
    /// let mut input = Cursor::new(b"1,1,1abc");
    /// let mut source = Source::new(input);
    /// let result = parse(parser, &mut source).unwrap();
    /// assert_eq!(result.len(), 3);
    /// ```
    pub fn with_joint(parser: P, joint: J) -> Self {
        Self {
            parser,
            min: 0,
            max: None,
            joint: Some(joint),
        }
    }

    /// Create a new Repeat parser with a joint parser and minimum repetitions.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let digit_parser = Literal::from_str("1");
    /// let comma_parser = Literal::from_str(",");
    ///
    /// // Parse at least 2 comma-separated values
    /// let parser = Repeat::with_joint_min(digit_parser, comma_parser, 2);
    /// let mut input = Cursor::new(b"1,1,1abc");
    /// let mut source = Source::new(input);
    /// let result = parse(parser, &mut source).unwrap();
    /// assert_eq!(result.len(), 3);
    /// ```
    pub fn with_joint_min(parser: P, joint: J, min: usize) -> Self {
        Self {
            parser,
            min,
            max: None,
            joint: Some(joint),
        }
    }

    /// Create a new Repeat parser with a joint parser and maximum repetitions.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let digit_parser = Literal::from_str("1");
    /// let comma_parser = Literal::from_str(",");
    ///
    /// // Parse at most 5 comma-separated values  
    /// let parser = Repeat::with_joint_max(digit_parser, comma_parser, 5);
    /// let mut input = Cursor::new(b"1,1,1,1,1,1,1abc");
    /// let mut source = Source::new(input);
    /// let result = parse(parser, &mut source).unwrap();
    /// assert_eq!(result.len(), 5); // stops at 5
    /// ```
    pub fn with_joint_max(parser: P, joint: J, max: usize) -> Self {
        Self {
            parser,
            min: 0,
            max: Some(max),
            joint: Some(joint),
        }
    }

    /// Create a new Repeat parser with a joint parser and both minimum and maximum repetitions.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use neotoma::{repeat::Repeat, literal::Literal, parser::{parse, Source}};
    /// use std::io::Cursor;
    ///
    /// let digit_parser = Literal::from_str("1");
    /// let comma_parser = Literal::from_str(",");
    ///
    /// // Parse 2-4 comma-separated values
    /// let parser = Repeat::with_joint_bounds(digit_parser, comma_parser, 2, 4);
    /// let mut input = Cursor::new(b"1,1,1,1,1abc");
    /// let mut source = Source::new(input);
    /// let result = parse(parser, &mut source).unwrap();
    /// assert_eq!(result.len(), 4); // stops at 4
    /// ```
    pub fn with_joint_bounds(parser: P, joint: J, min: usize, max: usize) -> Self {
        Self {
            parser,
            min,
            max: Some(max),
            joint: Some(joint),
        }
    }
}

impl<P, J, Ctx> Parser<Ctx> for Repeat<P, J>
where
    P: Parser<Ctx>,
    J: Parser<Ctx>,
{
    type Output = Vec<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);
        self.min.hash(&mut hasher);
        self.max.hash(&mut hasher);
        if let Some(ref joint) = self.joint {
            joint.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 mut results = Vec::new();

        // Parse the first element
        match self.parser.parse(source, cache, context) {
            Ok(result) => {
                results.push(result);
            }
            Err(Error::NoMatch) => {
                // No elements at all - check if this satisfies minimum
                if self.min == 0 {
                    return Ok(results);
                } else {
                    return Err(Error::NoMatch);
                }
            }
            Err(err) => return Err(err),
        }

        // Now parse joint + element pairs
        loop {
            // Try to parse the joint
            if let Some(ref joint) = self.joint {
                source.push();
                match joint.parse(source, cache, context) {
                    Ok(_) => {
                        // Joint matched, now try to parse another element
                        source.commit();

                        // Check max again after joint consumption
                        if let Some(max) = self.max {
                            if results.len() >= max {
                                // At max elements, trailing joint is allowed
                                break;
                            }
                        }

                        match self.parser.parse(source, cache, context) {
                            Ok(result) => {
                                // Successfully parsed another element
                                results.push(result);
                                // Continue the loop to try for more
                            }
                            Err(Error::NoMatch) => {
                                // No more elements after joint - trailing joint is allowed
                                break;
                            }
                            Err(err) => return Err(err),
                        }
                    }
                    Err(Error::NoMatch) => {
                        // No more joints - we're done
                        source.pop();
                        break;
                    }
                    Err(err) => return Err(err),
                }
            } else {
                if let Some(max) = self.max {
                    if results.len() >= max {
                        // At max elements, trailing joint is allowed
                        break;
                    }
                }

                match self.parser.parse(source, cache, context) {
                    Ok(result) => {
                        results.push(result);
                    }
                    Err(Error::NoMatch) => {
                        break;
                    }
                    Err(err) => return Err(err),
                }
            }
        }

        if results.len() < self.min {
            return Err(Error::NoMatch);
        }

        Ok(results)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{literal::Literal, parser::parse};
    use std::io::Cursor;

    #[test]
    fn test_id_implementation_different_repeat_parsers() {
        // This test checks that Repeat implements proper id() method
        // Repeat parsers with different parameters should have different IDs to avoid cache conflicts

        let repeat1 = Repeat::new(Literal::from_str("a"));
        let repeat2 = Repeat::new(Literal::from_str("b"));

        // These repeats have different inner parsers and should have different IDs
        // This test will FAIL if Repeat uses default id() implementation
        let id1 = <Repeat<crate::literal::Literal> as crate::parser::Parser<()>>::id(&repeat1);
        let id2 = <Repeat<crate::literal::Literal> as crate::parser::Parser<()>>::id(&repeat2);

        assert_ne!(
            id1, id2,
            "Different Repeat instances should have different IDs to avoid cache collisions"
        );
    }

    #[test]
    fn test_id_implementation_different_repeat_bounds() {
        // Test Repeat parsers with different bounds
        let repeat1 = Repeat::with_bounds(Literal::from_str("x"), 1, 3);
        let repeat2 = Repeat::with_bounds(Literal::from_str("x"), 2, 5);

        // These have the same inner parser but different bounds
        // They should have different IDs to avoid cache collisions
        // This test will FAIL if Repeat uses default id() implementation
        let id1 = <Repeat<crate::literal::Literal> as crate::parser::Parser<()>>::id(&repeat1);
        let id2 = <Repeat<crate::literal::Literal> as crate::parser::Parser<()>>::id(&repeat2);

        assert_ne!(
            id1, id2,
            "Repeat instances with different bounds should have different IDs to avoid cache collisions"
        );
    }

    #[test]
    fn test_id_implementation_repeat_with_different_joints() {
        // Test Repeat parsers with different joint parsers
        let repeat1 = Repeat::with_joint(Literal::from_str("item"), Literal::from_str(","));
        let repeat2 = Repeat::with_joint(Literal::from_str("item"), Literal::from_str(";"));

        // These have the same inner parser but different joint parsers
        // They should have different IDs to avoid cache collisions
        // This test will FAIL if Repeat uses default id() implementation
        let id1 =
            <Repeat<crate::literal::Literal, crate::literal::Literal> as crate::parser::Parser<
                (),
            >>::id(&repeat1);
        let id2 =
            <Repeat<crate::literal::Literal, crate::literal::Literal> as crate::parser::Parser<
                (),
            >>::id(&repeat2);

        assert_ne!(
            id1, id2,
            "Repeat instances with different joints should have different IDs to avoid cache collisions"
        );
    }

    #[test]
    fn test_id_implementation_same_repeat_parsers() {
        // Test that identical repeat parsers have the same ID
        let repeat1 = Repeat::new(Literal::from_str("a"));
        let repeat2 = Repeat::new(Literal::from_str("a"));

        assert_eq!(
            <Repeat<_> as crate::parser::Parser<()>>::id(&repeat1),
            <Repeat<_> as crate::parser::Parser<()>>::id(&repeat2),
            "Identical Repeat instances should have the same ID for cache efficiency"
        );
    }

    #[test]
    fn test_id_implementation_repeat_cache_correctness() {
        // This test verifies that cache works correctly without collisions
        // when Repeat implements proper id() method

        let repeat1 = Repeat::new(Literal::from_str("a"));
        let repeat2 = Repeat::new(Literal::from_str("b"));

        // Parse with first repeat parser
        let mut input1 = Cursor::new(b"aaa");
        let mut source1 = crate::parser::Source::new(&mut input1);

        let result1 = parse(repeat1, &mut source1);
        assert!(result1.is_ok(), "First parse should succeed");

        // Parse with second repeat parser at same position (0)
        // This should work correctly without cache collision
        let mut input2 = Cursor::new(b"bbb");
        let mut source2 = crate::parser::Source::new(&mut input2);

        let result2 = parse(repeat2, &mut source2);
        assert!(
            result2.is_ok(),
            "Second parse should succeed without cache collision"
        );

        // Verify results are correct (no cache collision occurred)
        if let (Ok(results1), Ok(results2)) = (result1, result2) {
            assert_eq!(results1.len(), 3);
            assert_eq!(results2.len(), 3);

            // Check that we got the right content
            assert_eq!(results1[0], b"a".as_slice().into());
            assert_eq!(results1[1], b"a".as_slice().into());
            assert_eq!(results1[2], b"a".as_slice().into());

            assert_eq!(results2[0], b"b".as_slice().into());
            assert_eq!(results2[1], b"b".as_slice().into());
            assert_eq!(results2[2], b"b".as_slice().into());
        } else {
            panic!("Both parses should succeed");
        }
    }

    #[test]
    fn test_repeat_basic_functionality() {
        // Basic functionality test to ensure Repeat works correctly
        let repeat = Repeat::new(Literal::from_str("a"));

        let mut input = Cursor::new(b"aaab");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 3);
        for item in result {
            assert_eq!(item, b"a".as_slice().into());
        }
    }

    #[test]
    fn test_repeat_with_min_functionality() {
        // Test Repeat with minimum bound
        let repeat = Repeat::with_min(Literal::from_str("x"), 2);

        // Should succeed with 3 matches
        let mut input1 = Cursor::new(b"xxxo");
        let mut source1 = crate::parser::Source::new(&mut input1);

        let result1 = parse(repeat, &mut source1).unwrap();
        assert_eq!(result1.len(), 3);

        // Should fail with only 1 match (below minimum)
        let repeat2 = Repeat::with_min(Literal::from_str("x"), 2);
        let mut input2 = Cursor::new(b"xo");
        let mut source2 = crate::parser::Source::new(&mut input2);

        let result2 = parse(repeat2, &mut source2);
        assert!(result2.is_err(), "Should fail when below minimum");
    }

    #[test]
    fn test_repeat_with_joint_functionality() {
        // Test Repeat with joint parser
        let repeat = Repeat::with_joint(Literal::from_str("item"), Literal::from_str(","));

        let mut input = Cursor::new(b"item,item,itemend");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 3);
        for item in result {
            assert_eq!(item, b"item".as_slice().into());
        }
    }

    #[test]
    fn test_repeat_with_max_functionality() {
        // Test Repeat with maximum bound
        let repeat = Repeat::with_max(Literal::from_str("x"), 2);

        let mut input = Cursor::new(b"xxxxxend");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 2); // Should stop at max of 2

        // Verify position advanced correctly
        let remaining = source.peek(3).unwrap();
        assert_eq!(remaining, b"xxx");
    }

    #[test]
    fn test_repeat_with_bounds_functionality() {
        // Test Repeat with both min and max bounds
        let repeat = Repeat::with_bounds(Literal::from_str("a"), 2, 4);

        // Should succeed with 3 matches (within bounds)
        let mut input1 = Cursor::new(b"aaaend");
        let mut source1 = crate::parser::Source::new(&mut input1);

        let result1 = parse(repeat, &mut source1).unwrap();
        assert_eq!(result1.len(), 3);

        // Should stop at max bound of 4
        let repeat2 = Repeat::with_bounds(Literal::from_str("a"), 2, 4);
        let mut input2 = Cursor::new(b"aaaaaaaaend");
        let mut source2 = crate::parser::Source::new(&mut input2);

        let result2 = parse(repeat2, &mut source2).unwrap();
        assert_eq!(result2.len(), 4);

        // Should fail with only 1 match (below minimum)
        let repeat3 = Repeat::with_bounds(Literal::from_str("a"), 2, 4);
        let mut input3 = Cursor::new(b"aend");
        let mut source3 = crate::parser::Source::new(&mut input3);

        let result3 = parse(repeat3, &mut source3);
        assert!(result3.is_err());
    }

    #[test]
    fn test_repeat_empty_input() {
        // Test with empty input
        let repeat = Repeat::new(Literal::from_str("a"));

        let mut input = Cursor::new(b"");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 0); // Should succeed with zero matches

        // Test with minimum requirement on empty input
        let repeat_min = Repeat::with_min(Literal::from_str("a"), 1);
        let mut input2 = Cursor::new(b"");
        let mut source2 = crate::parser::Source::new(&mut input2);

        let result2 = parse(repeat_min, &mut source2);
        assert!(result2.is_err());
    }

    #[test]
    fn test_repeat_zero_repetitions() {
        // Test case where inner parser immediately fails
        let repeat = Repeat::new(Literal::from_str("x"));

        let mut input = Cursor::new(b"aaaa");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 0);

        // Verify no input was consumed
        let remaining = source.peek(4).unwrap();
        assert_eq!(remaining, b"aaaa");
    }

    #[test]
    fn test_repeat_joint_with_bounds() {
        // Test joint parser with bounds
        let repeat =
            Repeat::with_joint_bounds(Literal::from_str("item"), Literal::from_str(","), 1, 3);

        let mut input = Cursor::new(b"item,item,itemend");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source);
        if result.is_ok() {
            let items = result.unwrap();
            assert!(!items.is_empty() && items.len() <= 3); // Should be within bounds
        } else {
            // If it fails, that's also a valid outcome for this complex scenario
            assert!(result.is_err());
        }
    }

    #[test]
    fn test_repeat_joint_trailing_separator() {
        // Test joint parser with trailing separator allowed
        let repeat = Repeat::with_joint(Literal::from_str("item"), Literal::from_str(","));

        let mut input = Cursor::new(b"item,item,item,end");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 3);

        // Trailing comma should be consumed
        let remaining = source.peek(3).unwrap();
        assert_eq!(remaining, b"end");
    }

    #[test]
    fn test_repeat_joint_no_trailing_separator() {
        // Test joint parser without trailing separator
        let repeat = Repeat::with_joint(Literal::from_str("item"), Literal::from_str(","));

        let mut input = Cursor::new(b"item,item,itemend");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 3);

        // No trailing comma, should stop at "end"
        let remaining = source.peek(3).unwrap();
        assert_eq!(remaining, b"end");
    }

    #[test]
    fn test_repeat_joint_single_item() {
        // Test joint parser with only one item (no joints)
        let repeat = Repeat::with_joint(Literal::from_str("item"), Literal::from_str(","));

        let mut input = Cursor::new(b"itemend");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 1);
        assert_eq!(result[0], b"item".as_slice().into());

        let remaining = source.peek(3).unwrap();
        assert_eq!(remaining, b"end");
    }

    #[test]
    fn test_repeat_joint_min_requirement() {
        // Test joint parser with minimum requirement
        let repeat = Repeat::with_joint_min(Literal::from_str("item"), Literal::from_str(","), 2);

        // Should succeed with 3 items
        let mut input1 = Cursor::new(b"item,item,itemend");
        let mut source1 = crate::parser::Source::new(&mut input1);

        let result1 = parse(repeat, &mut source1).unwrap();
        assert_eq!(result1.len(), 3);

        // Should fail with only 1 item
        let repeat2 = Repeat::with_joint_min(Literal::from_str("item"), Literal::from_str(","), 2);
        let mut input2 = Cursor::new(b"itemend");
        let mut source2 = crate::parser::Source::new(&mut input2);

        let result2 = parse(repeat2, &mut source2);
        assert!(result2.is_err());
    }

    #[test]
    fn test_repeat_position_tracking() {
        // Verify position is correctly tracked through repetitions
        let repeat = Repeat::new(Literal::from_str("ab"));

        let mut input = Cursor::new(b"ababab123");
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 3);

        // Position should be at '1'
        let next_byte = source.peek1().unwrap();
        assert_eq!(next_byte, b'1');
    }

    #[test]
    fn test_repeat_large_repetition_count() {
        // Test with a reasonably large number of repetitions
        let repeat = Repeat::new(Literal::from_str("x"));

        let large_input = b"x".repeat(1000);
        let mut input = Cursor::new(&large_input);
        let mut source = crate::parser::Source::new(&mut input);

        let result = parse(repeat, &mut source).unwrap();
        assert_eq!(result.len(), 1000);
    }
}