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/README.md b/README.md index 86f9a85f..d2565d9b 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 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. 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..ff18b71c 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, @@ -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;