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);
}
}