From 4731f2f164c570d94c0039b5ce79df0a19e6bffc Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 30 Oct 2025 23:21:08 +0000 Subject: [PATCH 1/4] Make nested_in more flexible (#899) * Make nested_in more flexible * Semver break --- Cargo.toml | 2 +- src/combinator.rs | 32 +++++++++++++++++++++++++------- src/lib.rs | 2 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b80d249..fe79b453 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 ", "Elijah Hartvigsen "] repository = "https://github.com/zesterer/chumsky" diff --git a/src/combinator.rs b/src/combinator.rs index e895a6f9..7018d205 100644 --- a/src/combinator.rs +++ b/src/combinator.rs @@ -1157,15 +1157,15 @@ where } /// See [`Parser::nested_in`]. -pub struct NestedIn { +pub struct NestedIn { 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 Copy for NestedIn {} -impl Clone for NestedIn { +impl Copy for NestedIn {} +impl Clone for NestedIn { fn clone(&self) -> Self { Self { parser_a: self.parser_a.clone(), @@ -1175,14 +1175,32 @@ impl Clone for NestedIn { } } -impl<'src, I, J, E, F, A, B, O> Parser<'src, I, O, E> for NestedIn +impl<'src, I, J, E, A, B, O> Parser<'src, I, O, E> for NestedIn 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 = >::Error, + State = >::State, + Context = >::Context, + >, + >::Error: Error<'src, J>, + >::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< + >::Error, + >::State, + >::Context, + >, + >, { #[inline(always)] fn go(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult { diff --git a/src/lib.rs b/src/lib.rs index 3751dc27..2eb6f77d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, J, F>(self, other: B) -> NestedIn + fn nested_in, J, F>(self, other: B) -> NestedIn where Self: Sized, I: 'src, From b489cd6882ac6206e7fd75369ed8e59ba8f9b718 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 1 Nov 2025 23:36:46 +0000 Subject: [PATCH 2/4] Added provenance and contribution sections to README --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 86f9a85f..cb3fa556 100644 --- a/README.md +++ b/README.md @@ -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 GPLv3 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. \ No newline at end of file From 0f2b61a90403a1acae036b9e5a612eef476438aa Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 2 Nov 2025 10:14:39 +0000 Subject: [PATCH 3/4] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cb3fa556..d2565d9b 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ This software is proudly and fondly written, maintained, used - and most crucial 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 GPLv3 may not guarantee warranty 'of any kind', you can at least use this software in the comforting knowledge +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 @@ -220,4 +220,4 @@ offloaded to metal and transistors. Source code is a **source of truth** - the o 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. \ No newline at end of file +not expect perfection, but we do expect you to personally understand your own motivations and decisions. From 45dbfc1dd3db7fea081d488b089476f46c9f442e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 2 Nov 2025 18:03:01 +0000 Subject: [PATCH 4/4] Basic implementation of recursive parsers macro --- src/lib.rs | 27 +++++++++++++++++++++ src/recursive.rs | 61 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2eb6f77d..ff18b71c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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(); + } } diff --git a/src/recursive.rs b/src/recursive.rs index b1969ebc..15547361 100644 --- a/src/recursive.rs +++ b/src/recursive.rs @@ -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 { +pub struct Recursive<'b, P: ?Sized> { inner: RecursiveInner

, + to_drop: Option>, } -impl<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I>> Recursive> { +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. /// @@ -103,9 +106,26 @@ impl<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I>> Recursive 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) { + 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] @@ -120,7 +140,7 @@ impl<'src, 'b, I: Input<'src>, O, E: ParserExtra<'src, I>> Recursive Recursive

{ +impl<'b, P: ?Sized> Recursive<'b, P> { #[inline] fn parser(&self) -> Rc

{ match &self.inner { @@ -132,13 +152,14 @@ impl Recursive

{ } } -impl Clone for Recursive

{ +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(), } } } @@ -154,7 +175,7 @@ pub(crate) fn recurse R>(f: F) -> R { f() } -impl<'src, I, O, E> Parser<'src, I, O, E> for Recursive> +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>, @@ -176,7 +197,7 @@ where go_extra!(O); } -impl<'src, I, O, E> Parser<'src, I, O, E> for Recursive> +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>, @@ -239,17 +260,18 @@ 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> +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>) -> A, + F: FnOnce(Recursive<'b, Direct<'src, 'b, I, O, E>>) -> A, { let rc = Rc::new_cyclic(|rc| { let rc: rc::Weak> = rc.clone() as _; let parser = Recursive { inner: RecursiveInner::Unowned(rc.clone()), + to_drop: None, }; f(parser) @@ -257,5 +279,28 @@ where 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;