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

Basic implementation of recursive parsers macro#902

Draft
zesterer wants to merge 4 commits intomainfrom
recursive-parsers-macro
Draft

Basic implementation of recursive parsers macro#902
zesterer wants to merge 4 commits intomainfrom
recursive-parsers-macro

Conversation

@zesterer
Copy link
Copy Markdown
Owner

@zesterer zesterer commented Nov 2, 2025

(The use of Unpin is arbitrary, I just wanted some dyn-compatible trait that worked for non-'static lifetimes)

@Zij-IT
Copy link
Copy Markdown
Contributor

Zij-IT commented Nov 3, 2025

After having played around with this version a bit, it has the same benefit that the (improved) recursive_n function does, which is "returning" multiple of the parsers, but without the tuple games that you mentioned. So... despite also normally shying away from macros, I think this one would be a great tool. rust-analyzer didn't seem to have any issues with the macro, which is a blessing. It may be worth considering offering the arm without = <expr> (if at all possible), so that the user gets auto-complete while trying to type names. It was the only real advantage I noticed while comparing the two (ignoring that the current impl doesn't allow for declaring parsers! multiple times in the same scope, but that fix is easy).

Neat solution :) Out of curiosity, what is your opinion on removing define and declare from the public API Recursive (either through renaming to _define and declare or deprecating the methods while mentioning the leak?

@zesterer
Copy link
Copy Markdown
Owner Author

zesterer commented Nov 3, 2025

I'm glad you like it, your recursive_n approach inspired me to come up with the drop closure solution.

It may be worth considering offering the arm without = <expr> (if at all possible), so that the user gets auto-complete while trying to type names.

Hmm, what do you mean by this? My editor doesn't have LSP support, so I don't really understand.

My plan is to add some way to include a type hint (like <name> $(: <type>)? = ...).

Out of curiosity, what is your opinion on removing define and declare from the public API Recursive (either through renaming to _define and declare or deprecating the methods while mentioning the leak?

Very much in favour. I've been of the view for a while that they're a stop-gap, and one that's easy to accidentally misuse. I think deprecation is fine for the time being (leaks aren't ideal, but they're not really an actual problem for the most-part), at least until a 1.0 release finally comes around.

@Zij-IT
Copy link
Copy Markdown
Contributor

Zij-IT commented Nov 3, 2025

Hmm, what do you mean by this? My editor doesn't have LSP support, so I don't really understand.

Ah. I would not survive programming Rust without my LSP, props to you. So, I mean:

// With `recursive_n` you effectively have to forward declare the parsers
recursive_n(|(expr, statement)|
    let expr = expr_atom(expr.clone(), , sta▍)
})

// With `parsers!` you are encouraged to finish the definition before moving to the next time
parsers! {
    expr = expr_atom(expr.clone(), sta▍)
}

For this example, we can treat ▍as the cursor position. At this point with recursive_n there is enough information to get auto-complete for statement where as it's not possible to do something similar with parsers! without doing statement = chumsky::prelude::todo() somewhere in the macro. Perhaps just having a way to forward declare like:

parsers! {
    declare: expr, statement;
    /* rest */
}

I don't think it's really a big problem, but just something I noticed :)

@nigeleke
Copy link
Copy Markdown

nigeleke commented Feb 23, 2026

I'm currently implementing chumsky alongside an existing pom implementation.
The recursive aspects of chumsky have become the biggest headache; possibly / probably because I'm doing things incorrectly and still learning.

However, pom seems to have a straightforward solution to this which is 'call':

https://docs.rs/pom/3.4.0/pom/parser/fn.call.html

Again, if I've understood correctly, this solves issues at point of call rather than at point of definition.

Is this a possible solution and/or addition in this instance?

Just for reference - my pom grammar is here https://github.com/nigeleke/fluent4rs/blob/main/src/grammar.rs
Look for call( to determine the recursion loop.

@zesterer
Copy link
Copy Markdown
Owner Author

Is this a possible solution and/or addition in this instance?

Unfortunately not in chumsky, for two reasons:

  1. Parsers might be expensive to create in chumsky, so creating them afresh every time a parser recurses is likely very slow. pom gets to 'hide' this cost because it uses dynamic dispatch for all parsers so, without putting too fine a point on it, it is... slow by default.

  2. Parsers in chumsky have a unique type, so including the type of a function which creates a new instance of the parser in the type of the parser is type recursion and produces an infinitely big type - something that Rust doesn't allow.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants