Skip to content
This repository was archived by the owner on Apr 2, 2026. It is now read-only.
Draft
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "chumsky"
version = "0.11.1"
version = "0.12.0"
description = "A parser library for humans with powerful error recovery"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>", "Elijah Hartvigsen <elijah.reed@hartvigsen.xyz", "Jakob Wiesmore <runetynan@gmail.com>"]
repository = "https://github.com/zesterer/chumsky"
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,23 @@ My apologies to Noam for choosing such an absurd name.
## License

Chumsky is licensed under the MIT license (see `LICENSE` in the main repository).

## Provenance

This software is proudly and fondly written, maintained, used - and most crucially - **understood** by real human beings.
While we can't personally attest to the provenance of every line of code ever contributed, the vast majority of the
codebase has certainly been developed without the aid of large language models and other stochastic 'intelligence'.

While the license may not guarantee warranty 'of any kind', you can at least use this software in the comforting knowledge
that its veracity and coherence is vouched for by sentient intelligence with skin in the game and a reputation to uphold.

## Contribution guidelines

We expect contributors to adhere to the ethos of the project.

Source code is not an artifact, an intermediate representation, nor a bothersome annoyance whose creation is to be
offloaded to metal and transistors. Source code is a **source of truth** - the only source of truth that constitutes this
software project - and it deserves to be understood and curated by the *accountable* and *reasoned* mind of a human being.

Please refrain from contributing changes that you have not personally understood and instigated the authorship of. We do
not expect perfection, but we do expect you to personally understand your own motivations and decisions.
32 changes: 25 additions & 7 deletions src/combinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1157,15 +1157,15 @@ where
}

/// See [`Parser::nested_in`].
pub struct NestedIn<A, B, J, F, O, E> {
pub struct NestedIn<A, B, J, O, E> {
pub(crate) parser_a: A,
pub(crate) parser_b: B,
#[allow(dead_code)]
pub(crate) phantom: EmptyPhantom<(J, F, O, E)>,
pub(crate) phantom: EmptyPhantom<(J, O, E)>,
}

impl<A: Copy, B: Copy, J, F, O, E> Copy for NestedIn<A, B, J, F, O, E> {}
impl<A: Clone, B: Clone, J, F, O, E> Clone for NestedIn<A, B, J, F, O, E> {
impl<A: Copy, B: Copy, J, O, E> Copy for NestedIn<A, B, J, O, E> {}
impl<A: Clone, B: Clone, J, O, E> Clone for NestedIn<A, B, J, O, E> {
fn clone(&self) -> Self {
Self {
parser_a: self.parser_a.clone(),
Expand All @@ -1175,14 +1175,32 @@ impl<A: Clone, B: Clone, J, F, O, E> Clone for NestedIn<A, B, J, F, O, E> {
}
}

impl<'src, I, J, E, F, A, B, O> Parser<'src, I, O, E> for NestedIn<A, B, J, F, O, E>
impl<'src, I, J, E, A, B, O> Parser<'src, I, O, E> for NestedIn<A, B, J, O, E>
where
I: Input<'src>,
E: ParserExtra<'src, I>,
// These bounds looks silly, but they basically just ensure that the extra type of the inner parser is compatible with the extra of the outer parser
E: ParserExtra<
'src,
J,
Error = <E as ParserExtra<'src, I>>::Error,
State = <E as ParserExtra<'src, I>>::State,
Context = <E as ParserExtra<'src, I>>::Context,
>,
<E as ParserExtra<'src, I>>::Error: Error<'src, J>,
<E as ParserExtra<'src, I>>::State: Inspector<'src, J>,
B: Parser<'src, I, J, E>,
J: Input<'src>,
F: ParserExtra<'src, J, State = E::State, Context = E::Context, Error = E::Error>,
A: Parser<'src, J, O, F>,
A: Parser<
'src,
J,
O,
extra::Full<
<E as ParserExtra<'src, I>>::Error,
<E as ParserExtra<'src, I>>::State,
<E as ParserExtra<'src, I>>::Context,
>,
>,
{
#[inline(always)]
fn go<M: Mode>(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult<M, O> {
Expand Down
29 changes: 28 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@ pub trait Parser<'src, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Defau
///
/// assert_eq!(tl.parse(&tokens).into_result(), Ok(vec![("foo", vec!["a", "b"])]));
/// ```
fn nested_in<B: Parser<'src, J, I, F>, J, F>(self, other: B) -> NestedIn<Self, B, J, F, O, E>
fn nested_in<B: Parser<'src, J, I, F>, J, F>(self, other: B) -> NestedIn<Self, B, I, O, F>
where
Self: Sized,
I: 'src,
Expand Down Expand Up @@ -4130,4 +4130,31 @@ mod tests {

let _ = parser().parse("tru");
}

// Prevent a regression
#[test]
fn parsers_macro() {
use crate::recursive::parsers;

fn parsers<'i>() -> (
impl Parser<'i, &'i str, &'i str>,
impl Parser<'i, &'i str, &'i str>,
) {
parsers! {
a = just("a")
.then(b.or_not().delimited_by(just('('), just(')')))
.to_slice();

b = just("b")
.then(a.or_not().delimited_by(just('('), just(')')))
.to_slice();
};

(a, b)
}

let (a, b) = parsers();

a.parse("a(b(a(b())))").unwrap();
}
}
61 changes: 53 additions & 8 deletions src/recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,14 @@ pub struct Indirect<'src, 'b, I: Input<'src>, O, Extra: ParserExtra<'src, I>> {
/// [definition](Recursive::define).
///
/// Prefer to use [`recursive()`], which exists as a convenient wrapper around both operations, if possible.
pub struct Recursive<P: ?Sized> {
pub struct Recursive<'b, P: ?Sized> {
inner: RecursiveInner<P>,
to_drop: Option<Rc<dyn Unpin + 'b>>,
}

impl<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I>> Recursive<Indirect<'src, 'b, I, O, E>> {
impl<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I>>
Recursive<'b, Indirect<'src, 'b, I, O, E>>
{
/// Declare the existence of a recursive parser, allowing it to be used to construct parser combinators before
/// being fulled defined.
///
Expand Down Expand Up @@ -103,9 +106,26 @@ impl<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I>> Recursive<Indirect<'s
inner: RecursiveInner::Owned(Rc::new(Indirect {
inner: OnceCell::new(),
})),
to_drop: None,
}
}

#[doc(hidden)]
pub fn _weak_clone(&self) -> Self {
Self {
inner: match &self.inner {
RecursiveInner::Owned(x) => RecursiveInner::Unowned(Rc::downgrade(x)),
RecursiveInner::Unowned(x) => RecursiveInner::Unowned(x.clone()),
},
to_drop: None,
}
}

#[doc(hidden)]
pub fn _set_to_drop(&mut self, x: Rc<dyn Unpin + 'b>) {
self.to_drop = Some(x);
}

/// Defines the parser after declaring it, allowing it to be used for parsing.
// INFO: Clone bound not actually needed, but good to be safe for future compat
#[track_caller]
Expand All @@ -120,7 +140,7 @@ impl<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I>> Recursive<Indirect<'s
}
}

impl<P: ?Sized> Recursive<P> {
impl<'b, P: ?Sized> Recursive<'b, P> {
#[inline]
fn parser(&self) -> Rc<P> {
match &self.inner {
Expand All @@ -132,13 +152,14 @@ impl<P: ?Sized> Recursive<P> {
}
}

impl<P: ?Sized> Clone for Recursive<P> {
impl<'b, P: ?Sized> Clone for Recursive<'b, P> {
fn clone(&self) -> Self {
Self {
inner: match &self.inner {
RecursiveInner::Owned(x) => RecursiveInner::Owned(x.clone()),
RecursiveInner::Unowned(x) => RecursiveInner::Unowned(x.clone()),
},
to_drop: self.to_drop.clone(),
}
}
}
Expand All @@ -154,7 +175,7 @@ pub(crate) fn recurse<R, F: FnOnce() -> R>(f: F) -> R {
f()
}

impl<'src, I, O, E> Parser<'src, I, O, E> for Recursive<Indirect<'src, '_, I, O, E>>
impl<'b, 'src, I, O, E> Parser<'src, I, O, E> for Recursive<'b, Indirect<'src, 'b, I, O, E>>
where
I: Input<'src>,
E: ParserExtra<'src, I>,
Expand All @@ -176,7 +197,7 @@ where
go_extra!(O);
}

impl<'src, I, O, E> Parser<'src, I, O, E> for Recursive<Direct<'src, '_, I, O, E>>
impl<'b, 'src, I, O, E> Parser<'src, I, O, E> for Recursive<'b, Direct<'src, 'b, I, O, E>>
where
I: Input<'src>,
E: ParserExtra<'src, I>,
Expand Down Expand Up @@ -239,23 +260,47 @@ where
/// ])));
/// ```
// INFO: Clone bound not actually needed, but good to be safe for future compat
pub fn recursive<'src, 'b, I, O, E, A, F>(f: F) -> Recursive<Direct<'src, 'b, I, O, E>>
pub fn recursive<'src, 'b, I, O, E, A, F>(f: F) -> Recursive<'b, Direct<'src, 'b, I, O, E>>
where
I: Input<'src>,
E: ParserExtra<'src, I>,
A: Parser<'src, I, O, E> + Clone + 'b,
F: FnOnce(Recursive<Direct<'src, 'b, I, O, E>>) -> A,
F: FnOnce(Recursive<'b, Direct<'src, 'b, I, O, E>>) -> A,
{
let rc = Rc::new_cyclic(|rc| {
let rc: rc::Weak<DynParser<'src, 'b, I, O, E>> = rc.clone() as _;
let parser = Recursive {
inner: RecursiveInner::Unowned(rc.clone()),
to_drop: None,
};

f(parser)
});

Recursive {
inner: RecursiveInner::Owned(rc),
to_drop: None,
}
}

/// TODO
#[macro_export]
macro_rules! parsers {
($($name:ident = $body:expr;)*) => {
struct Parsers<$($name,)*> { $($name: $name,)* }
let mut parsers = Parsers { $($name: Recursive::declare(),)* };
{
$(let $name = parsers.$name._weak_clone();)*
$(parsers.$name.define($body);)*
}
{
let to_drop = ::alloc::rc::Rc::new({
$(let $name = parsers.$name.clone();)*
move || { $(::core::mem::drop($name);)* }
});
$(parsers.$name._set_to_drop(to_drop.clone() as _);)*
}
let Parsers { $($name,)* } = parsers;
};
}
pub use parsers;
Loading