Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,8 @@ where
errors: &mut self.errors,
state: &mut self.state,
ctx: &self.ctx,
#[cfg(debug_assertions)]
rec_data: None,
#[cfg(feature = "memoization")]
memos: &mut self.memos,
}
Expand All @@ -666,6 +668,8 @@ where
errors: &mut self.errors,
state: &mut self.state,
ctx: &self.ctx,
#[cfg(debug_assertions)]
rec_data: None,
#[cfg(feature = "memoization")]
memos: &mut self.memos,
}
Expand All @@ -687,6 +691,8 @@ pub struct InputRef<'a, 'parse, I: Input<'a>, E: ParserExtra<'a, I>> {
pub(crate) errors: &'parse mut Errors<I::Offset, E::Error>,
pub(crate) state: &'parse mut E::State,
pub(crate) ctx: &'parse E::Context,
#[cfg(debug_assertions)]
pub(crate) rec_data: Option<(I::Offset, usize)>,
#[cfg(feature = "memoization")]
pub(crate) memos: &'parse mut HashMap<(I::Offset, usize), Option<Located<I::Offset, E::Error>>>,
}
Expand All @@ -708,6 +714,8 @@ impl<'a, 'parse, I: Input<'a>, E: ParserExtra<'a, I>> InputRef<'a, 'parse, I, E>
state: self.state,
ctx: new_ctx,
errors: self.errors,
#[cfg(debug_assertions)]
rec_data: self.rec_data,
#[cfg(feature = "memoization")]
memos: self.memos,
};
Expand Down Expand Up @@ -735,6 +743,8 @@ impl<'a, 'parse, I: Input<'a>, E: ParserExtra<'a, I>> InputRef<'a, 'parse, I, E>
state: self.state,
ctx: self.ctx,
errors: self.errors,
#[cfg(debug_assertions)]
rec_data: None,
#[cfg(feature = "memoization")]
memos,
};
Expand Down
68 changes: 44 additions & 24 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2306,28 +2306,50 @@ mod tests {
mod debug_asserts {
use super::prelude::*;

// TODO panic when left recursive parser is detected
// #[test]
// #[should_panic]
// fn debug_assert_left_recursive() {
// recursive(|expr| {
// let atom = any::<&str, extra::Default>()
// .filter(|c: &char| c.is_alphabetic())
// .repeated()
// .at_least(1)
// .collect();

// let sum = expr
// .clone()
// .then_ignore(just('+'))
// .then(expr)
// .map(|(a, b)| format!("{}{}", a, b));

// sum.or(atom)
// })
// .then_ignore(end())
// .parse("a+b+c");
// }
#[test]
#[should_panic]
fn debug_assert_left_recursive() {
recursive(|expr| {
let atom = any::<&str, extra::Default>()
.filter(|c: &char| c.is_alphabetic())
.repeated()
.at_least(1)
.collect();

let sum = expr
.clone()
.then_ignore(just('+'))
.then(expr)
.map(|(a, b)| format!("{}{}", a, b));

sum.or(atom)
})
.then_ignore(end())
.parse("a+b+c");
}

#[test]
#[should_panic]
fn debug_assert_left_recursive_delc() {
let mut expr = Recursive::declare();
expr.define({
let atom = any::<&str, extra::Default>()
.filter(|c: &char| c.is_alphabetic())
.repeated()
.at_least(1)
.collect();

let sum = expr
.clone()
.then_ignore(just('+'))
.then(expr.clone())
.map(|(a, b)| format!("{}{}", a, b));

sum.or(atom)
});

expr.then_ignore(end()).parse("a+b+c");
}

#[test]
#[should_panic]
Expand Down Expand Up @@ -2375,7 +2397,5 @@ mod tests {
.repeated()
.parse("a+b+c");
}

// TODO what about IterConfigure and TryIterConfigure?
}
}
26 changes: 26 additions & 0 deletions src/recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub struct Indirect<'a, 'b, I: Input<'a>, O, Extra: ParserExtra<'a, I>> {
/// Prefer to use [`recursive()`], which exists as a convenient wrapper around both operations, if possible.
pub struct Recursive<P: ?Sized> {
inner: RecursiveInner<P>,
location: Location<'static>,
}

impl<'a, 'b, I: Input<'a>, O, E: ParserExtra<'a, I>> Recursive<Indirect<'a, 'b, I, O, E>> {
Expand Down Expand Up @@ -123,11 +124,13 @@ impl<'a, 'b, I: Input<'a>, O, E: ParserExtra<'a, I>> Recursive<Indirect<'a, 'b,
/// Ok(Chain::Link('+', Box::new(Chain::Link('+', Box::new(Chain::End))))),
/// );
/// ```
#[track_caller]
pub fn declare() -> Self {
Recursive {
inner: RecursiveInner::Owned(RefC::new(Indirect {
inner: OnceCell::new(),
})),
location: *Location::caller(),
}
}

Expand Down Expand Up @@ -160,6 +163,7 @@ impl<P: ?Sized> Clone for Recursive<P> {
RecursiveInner::Owned(x) => RecursiveInner::Owned(x.clone()),
RecursiveInner::Unowned(x) => RecursiveInner::Unowned(x.clone()),
},
location: self.location,
}
}
}
Expand All @@ -182,6 +186,15 @@ where
{
#[inline]
fn go<M: Mode>(&self, inp: &mut InputRef<'a, '_, I, E>) -> PResult<M, O> {
#[cfg(debug_assertions)]
{
// TODO: Don't use address, since this might not be constant?
let new_rec_data = Some((inp.offset, &self.inner as *const _ as *const () as usize));
if inp.rec_data == new_rec_data {
panic!("found left recursive parser at {}", self.location)
}
inp.rec_data = new_rec_data;
}
recurse(move || {
M::invoke(
self.parser()
Expand All @@ -204,6 +217,15 @@ where
{
#[inline]
fn go<M: Mode>(&self, inp: &mut InputRef<'a, '_, I, E>) -> PResult<M, O> {
#[cfg(debug_assertions)]
{
// TODO: Don't use address, since this might not be constant?
let new_rec_data = Some((inp.offset, &self.inner as *const _ as *const () as usize));
if inp.rec_data == new_rec_data {
panic!("found left recursive parser at {}", self.location)
}
inp.rec_data = new_rec_data;
}
recurse(move || M::invoke(&*self.parser(), inp))
}

Expand Down Expand Up @@ -260,23 +282,27 @@ where
/// ])));
/// ```
// INFO: Clone bound not actually needed, but good to be safe for future compat
#[track_caller]
pub fn recursive<'a, 'b, I, O, E, A, F>(f: F) -> Recursive<Direct<'a, 'b, I, O, E>>
where
I: Input<'a>,
E: ParserExtra<'a, I>,
A: Parser<'a, I, O, E> + Clone + MaybeSync + 'b,
F: FnOnce(Recursive<Direct<'a, 'b, I, O, E>>) -> A,
{
let location = *Location::caller();
let rc = RefC::new_cyclic(|rc| {
let rc: RefW<DynParser<'a, 'b, I, O, E>> = rc.clone() as _;
let parser = Recursive {
inner: RecursiveInner::Unowned(rc.clone()),
location,
};

f(parser)
});

Recursive {
inner: RecursiveInner::Owned(rc),
location,
}
}