Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.

Commit f7c378e

Browse files
committed
Add recursive_fold and recursive_2 to prevent memory leaks
1 parent 4731f2f commit f7c378e

2 files changed

Lines changed: 193 additions & 12 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/recursive.rs

Lines changed: 192 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ impl<P: ?Sized> Recursive<P> {
130130
.expect("Recursive parser used before being defined"),
131131
}
132132
}
133+
134+
fn weak(&self) -> Self {
135+
Self {
136+
inner: match &self.inner {
137+
RecursiveInner::Owned(x) => RecursiveInner::Unowned(Rc::downgrade(x)),
138+
RecursiveInner::Unowned(x) => RecursiveInner::Unowned(x.clone()),
139+
},
140+
}
141+
}
133142
}
134143

135144
impl<P: ?Sized> Clone for Recursive<P> {
@@ -239,23 +248,195 @@ where
239248
/// ])));
240249
/// ```
241250
// INFO: Clone bound not actually needed, but good to be safe for future compat
242-
pub fn recursive<'src, 'b, I, O, E, A, F>(f: F) -> Recursive<Direct<'src, 'b, I, O, E>>
251+
pub fn recursive<'src, 'b, I, O, E, A, F>(f: F) -> Recursive<Indirect<'src, 'b, I, O, E>>
243252
where
244253
I: Input<'src>,
245254
E: ParserExtra<'src, I>,
246255
A: Parser<'src, I, O, E> + Clone + 'b,
247-
F: FnOnce(Recursive<Direct<'src, 'b, I, O, E>>) -> A,
256+
F: FnOnce(Recursive<Indirect<'src, 'b, I, O, E>>) -> A,
257+
{
258+
recursive_fold(|recursive| (f(recursive), ())).0
259+
}
260+
/// Construct two mutually recursive parsers (i.e: two parser that may reference each other).
261+
///
262+
/// The given function must create the parser. The parser must not be used to parse input before this function returns.
263+
///
264+
/// The output type of this parser is `O1`, the same as the inner parser.
265+
///
266+
/// # Examples
267+
///
268+
/// ```
269+
/// # use chumsky::prelude::*;
270+
/// use chumsky::recursive::recursive_2;
271+
/// #[derive(Debug, PartialEq)]
272+
/// enum Tree<'a> {
273+
/// Leaf(&'a str),
274+
/// Branch(Vec<Tree<'a>>),
275+
/// }
276+
///
277+
/// // Parser that recursively parses nested lists with ordinary or square brackets
278+
/// let tree = recursive_2::<_, _, _, extra::Err<Simple<char>>, _, _, _, _>(|bracket, square| {
279+
/// (choice((bracket.clone()
280+
/// .separated_by(just(','))
281+
/// .collect::<Vec<_>>()
282+
/// .delimited_by(just('('), just(')'))
283+
/// .map(Tree::Branch)
284+
/// .or(text::ascii::ident().map(Tree::Leaf))
285+
/// .padded(), square.clone())),
286+
/// choice((square
287+
/// .separated_by(just(','))
288+
/// .collect::<Vec<_>>()
289+
/// .delimited_by(just('['), just(']'))
290+
/// .map(Tree::Branch)
291+
/// .or(text::ascii::ident().map(Tree::Leaf))
292+
/// .padded(), bracket.clone())))
293+
/// });
294+
///
295+
/// assert_eq!(tree.parse("hello").into_result(), Ok(Tree::Leaf("hello")));
296+
/// assert_eq!(tree.parse("(a, b, c)").into_result(), Ok(Tree::Branch(vec![
297+
/// Tree::Leaf("a"),
298+
/// Tree::Leaf("b"),
299+
/// Tree::Leaf("c"),
300+
/// ])));
301+
/// // The parser can deal with arbitrarily complex nested lists
302+
/// assert_eq!(tree.parse("[(a, b), c, [d, (e, f)]]").into_result(), Ok(Tree::Branch(vec![
303+
/// Tree::Branch(vec![
304+
/// Tree::Leaf("a"),
305+
/// Tree::Leaf("b"),
306+
/// ]),
307+
/// Tree::Leaf("c"),
308+
/// Tree::Branch(vec![
309+
/// Tree::Leaf("d"),
310+
/// Tree::Branch(vec![
311+
/// Tree::Leaf("e"),
312+
/// Tree::Leaf("f"),
313+
/// ]),
314+
/// ]),
315+
/// ])));
316+
/// // Both sides of the parser can be used
317+
/// assert_eq!(tree.flip().parse("(a, b, c)").into_result(), Ok(Tree::Branch(vec![
318+
/// Tree::Leaf("a"),
319+
/// Tree::Leaf("b"),
320+
/// Tree::Leaf("c"),
321+
/// ])));
322+
/// ```
323+
///
324+
///
325+
pub fn recursive_2<'src, 'b, I, O1, O2, E, A1, A2, R, F>(
326+
f: F,
327+
) -> EitherParser<Recursive<Indirect<'src, 'b, I, O1, E>>, Recursive<Indirect<'src, 'b, I, O2, E>>>
328+
where
329+
I: Input<'src>,
330+
E: ParserExtra<'src, I>,
331+
A1: Parser<'src, I, O1, E> + Clone + 'b,
332+
A2: Parser<'src, I, O2, E> + Clone + 'b,
333+
R: Into<EitherParser<A1, A2>>,
334+
F: FnOnce(
335+
Recursive<Indirect<'src, 'b, I, O1, E>>,
336+
Recursive<Indirect<'src, 'b, I, O2, E>>,
337+
) -> R,
338+
{
339+
recursive_fold(|left| recursive_fold(|right| f(left, right).into().flip()).flip())
340+
}
341+
342+
/// Construct an EitherParser<A1, A2> with A1 recursive and a guarantee that A1 and A2 are dropped together.
343+
/// This function is useful for constructing recursive parser combinators.
344+
///
345+
/// The given function must create the parser. The parser must not be used to parse input before this function returns.
346+
///
347+
/// The output type of this parser is `O1`, the same as the left parser.
348+
///
349+
/// # Examples
350+
///
351+
/// Please see [recursive] and [recursive_2] for how recursive_fold can be used to define new recursive combinators.
352+
pub fn recursive_fold<'a, 'b, I, O1, A1, A2, R, F, E>(
353+
f: F,
354+
) -> EitherParser<Recursive<Indirect<'a, 'b, I, O1, E>>, A2>
355+
where
356+
I: Input<'a>,
357+
E: ParserExtra<'a, I>,
358+
A1: Parser<'a, I, O1, E> + Clone + 'b,
359+
R: Into<EitherParser<A1, A2>>,
360+
F: FnOnce(Recursive<Indirect<'a, 'b, I, O1, E>>) -> R,
361+
{
362+
let mut left_parser = Recursive::declare();
363+
let either = f(left_parser.weak()).into();
364+
left_parser.define(either.0);
365+
EitherParser(left_parser, either.1)
366+
}
367+
368+
/// A parser that parses using A1 while guaranteeing that A1 and A2 are dropped together.
369+
#[derive(Clone)]
370+
pub struct EitherParser<A1, A2>(A1, A2);
371+
372+
impl<A1, A2> From<(A1, A2)> for EitherParser<A1, A2> {
373+
fn from(value: (A1, A2)) -> Self {
374+
EitherParser(value.0, value.1)
375+
}
376+
}
377+
378+
impl<'src, I, O, E, A1, A2> Parser<'src, I, O, E> for EitherParser<A1, A2>
379+
where
380+
I: Input<'src>,
381+
E: ParserExtra<'src, I>,
382+
A1: Parser<'src, I, O, E>,
248383
{
249-
let rc = Rc::new_cyclic(|rc| {
250-
let rc: rc::Weak<DynParser<'src, 'b, I, O, E>> = rc.clone() as _;
251-
let parser = Recursive {
252-
inner: RecursiveInner::Unowned(rc.clone()),
253-
};
384+
fn go<M: Mode>(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult<M, O> {
385+
self.0.go::<M>(inp)
386+
}
387+
388+
go_extra!(O);
389+
}
390+
391+
impl<A1, A2> EitherParser<A1, A2> {
392+
/// Flip the position of A1 and A2
393+
pub fn flip(self) -> EitherParser<A2, A1> {
394+
EitherParser(self.1, self.0)
395+
}
396+
397+
#[allow(missing_docs)]
398+
pub fn left(&self) -> &A1 {
399+
&self.0
400+
}
401+
402+
#[allow(missing_docs)]
403+
pub fn right(&self) -> &A2 {
404+
&self.1
405+
}
406+
}
254407

255-
f(parser)
256-
});
408+
#[cfg(test)]
409+
mod tests {
410+
use crate::prelude::*;
411+
use crate::recursive::{recursive_2, RecursiveInner};
412+
413+
#[test]
414+
fn no_strong_references_after_drop() {
415+
fn check_ref_count<T>(recursive_inner: &RecursiveInner<T>, expected: usize) {
416+
match recursive_inner {
417+
RecursiveInner::Owned(_) => {
418+
panic!("a user must not have access to strong references")
419+
}
420+
RecursiveInner::Unowned(weak) => {
421+
assert_eq!(weak.strong_count(), expected);
422+
}
423+
};
424+
}
425+
426+
let mut smuggle_r1 = None;
427+
let mut smuggle_r2 = None;
428+
429+
{
430+
let _ = recursive_2::<&str, (), (), extra::Err<Simple<char>>, _, _, _, _>(|r1, r2| {
431+
check_ref_count(&r1.inner, 1);
432+
check_ref_count(&r2.inner, 1);
433+
smuggle_r1.replace(r1.clone());
434+
smuggle_r2.replace(r2.clone());
435+
(r1, r2)
436+
});
437+
}
257438

258-
Recursive {
259-
inner: RecursiveInner::Owned(rc),
439+
check_ref_count(&smuggle_r1.unwrap().inner, 0);
440+
check_ref_count(&smuggle_r2.unwrap().inner, 0);
260441
}
261442
}

0 commit comments

Comments
 (0)