Skip to content

pest to chumsky migration#185

Open
gerau wants to merge 8 commits intoBlockstreamResearch:masterfrom
distributed-lab:simc/chumsky-migration
Open

pest to chumsky migration#185
gerau wants to merge 8 commits intoBlockstreamResearch:masterfrom
distributed-lab:simc/chumsky-migration

Conversation

@gerau
Copy link
Contributor

@gerau gerau commented Dec 18, 2025

No description provided.

@apoelstra
Copy link
Contributor

cc @canndrew may want to keep an eye on progress here

@gerau
Copy link
Contributor Author

gerau commented Jan 12, 2026

Right now there is a working parser using the chumsky crate which replicates the behavior of the pest parser in terms of building a correct parse tree -- it should produce the same Simplicity program. This implementation also fixes #79.

Error reporting is currently broken because we need to replace the logic of parse::ParseFromStr to return multiple errors or handle recoverable errors differently, and error recovery is proving to be more overwhelming than I estimated it would be.

The code will be refactored because some parts are only half-finished (such as adding Spanned for certain names) and there are better ways to use parser combinators. However, I want to show this progress before implementing error recovery.

@gerau
Copy link
Contributor Author

gerau commented Jan 12, 2026

cc @canndrew

@gerau gerau force-pushed the simc/chumsky-migration branch from 1b1e751 to 1e7c61b Compare January 14, 2026 15:10
@canndrew
Copy link
Contributor

It's weird that the lexer is treating all our built-in macro/function/etc names as being keywords. I realize that's how the compiler currently works, so it's okay to land this PR as-is to keep the changes small. But obviously we'd want to eventually treat these as just being identifiers.

@gerau gerau force-pushed the simc/chumsky-migration branch 3 times, most recently from bd5c30f to 24a6bc6 Compare January 21, 2026 13:08
@gerau
Copy link
Contributor Author

gerau commented Jan 21, 2026

I would like to provide more context on a few points:

  1. Some of the parsers try to recover to some "default" values, so it could continue parse and report an error. If I understand correctly, in most parsers this is implemented by adding to parsing structures error states, so analysis stage of the compiler could handle this cases correctly. I haven't done this in this PR, because it requires changing the analysis code as well. Right now, it would not progress to analysis stage if there is a parsing error.

  2. I changed the lexer to not parse built-in types and functions as keywords, because this creates behavior, that was not in original pest parser (e.g. u1 was considered UnsignedType, even if it's defined as variable). This also does not require significant changes to parser itself, so I think we should keep this change here.

  3. I didn't change errors too much and their printing, but I think we should consider refactor errors and use ariadne for collecting them and printing. It seems to pair fairly well with chumsky, and it would provide prettier errors than we currently have.

@gerau
Copy link
Contributor Author

gerau commented Jan 21, 2026

Also a note about performance: chumsky seems faster in general than pest parser. For example, on my machine for a large file .simf file, which was generated by simplicity-bn254, chumsky is 10 times faster than pest for parsing. But trade-off for this is slower compilation times and lag with rust-analyzer, because chumsky is type-driven.

It would be nice if we could move the parser to a different crate, so it would not affect compile time too much, and the SimplicityHL parser could be used separately from the compiler.

@gerau
Copy link
Contributor Author

gerau commented Jan 21, 2026

cc @canndrew @KyrylR

@apoelstra
Copy link
Contributor

apoelstra commented Jan 23, 2026

It would be nice if we could move the parser to a different crate, so it would not affect compile time too much, and the SimplicityHL parser could be used separately from the compiler.

Strongly agreed. If we had a public somewhat-standard AST type that the parser would produce, this would also let people implement some kinds of linters and/or formatters without needing support from us. (We will likely get some pressure to preserve whitespace and comments to help with this. Maybe we actually want two AST types, one that has whitespace and comments and one that's reduced somehow.)

In any case, this is all separate from this PR.

@gerau gerau force-pushed the simc/chumsky-migration branch from 24a6bc6 to b200640 Compare January 27, 2026 11:50
@KyrylR
Copy link
Collaborator

KyrylR commented Jan 29, 2026

Please rebase onto master

@gerau gerau force-pushed the simc/chumsky-migration branch 2 times, most recently from b88ef62 to 4bf6252 Compare January 29, 2026 13:52
@gerau gerau force-pushed the simc/chumsky-migration branch 2 times, most recently from 6a5bc25 to 7e5a257 Compare January 30, 2026 13:07
@KyrylR
Copy link
Collaborator

KyrylR commented Feb 2, 2026

Overall, it looks good to me. I’m still concerned about whether the Options will remain the same. Could you please take the time to add regression tests using Options, DCD, Option Offer, and Lending Contract?

@gerau gerau force-pushed the simc/chumsky-migration branch from 8270f13 to 1833a7a Compare February 3, 2026 11:28
@gerau
Copy link
Contributor Author

gerau commented Feb 3, 2026

Overall, it looks good to me. I’m still concerned about whether the Options will remain the same. Could you please take the time to add regression tests using Options, DCD, Option Offer, and Lending Contract?

I found a bug in my code, so thank you for pushing me to test it. I've run the tests within the clone of simplicity-contracts repo; you can run them yourself to verify the results. It simply runs all the tests in this repo using the new parser and checks if the resulting bytecode is identical in the load_program function.

@gerau gerau force-pushed the simc/chumsky-migration branch from 1833a7a to 60bd05a Compare February 3, 2026 12:53
@KyrylR
Copy link
Collaborator

KyrylR commented Feb 3, 2026

Overall, it looks good to me. I’m still concerned about whether the Options will remain the same. Could you please take the time to add regression tests using Options, DCD, Option Offer, and Lending Contract?

I found a bug in my code, so thank you for pushing me to test it. I've run the tests within the clone of simplicity-contracts repo; you can run them yourself to verify the results. It simply runs all the tests in this repo using the new parser and checks if the resulting bytecode is identical in the load_program function.

Cool, thanks

ACK 60bd05a

@apoelstra
Copy link
Contributor

In d9d2b1b:

There are tons of lockfile changes in this commit which have nothing to do with the chumsky addition.

As an aside, when adding dependencies alongside nontrivial code changes, it's easier for me to review when the Cargo.toml/Cargo.lock changes are in their own commit, and the "real" code is in a subsequent commit.

src/lexer.rs Outdated
@@ -0,0 +1,319 @@
use chumsky::prelude::*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In d9d2b1b:

In general I am very suspicious of wildcard imports. They make it very hard to tell where symbols are coming from, especially words like any and just. If there is only one of them in the file I guess that's okay.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a later PR we can remove this (and add the clippy lint to eliminate all the wildcards).

Cargo.toml Outdated
arbitrary = { version = "1", optional = true, features = ["derive"] }
clap = "4.5.37"
chumsky = "0.11.2"
line-index = "0.1.2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 1ae8b0d:

This crate seems super sketchy. It uses u32 to represent lengths all over the place, deals with this mismatch with undocumented panics (your own code uses integer type casts, which are a code smell and which occur almost nowhere else in this codebase).

It seems like this is used exclusively for a single Display impl? Let's just drop this dependency tree and manually count lines.

src/parse.rs Outdated
impl PestParse for Item {
const RULE: Rule = Rule::item;
let Some(tokens) = tokens else {
return Err(lex_errs.first().cloned().unwrap_or(RichError::new(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 1ae8b0d:

.first().cloned() can be replaced with .swap_remove(0) or even just .pop() because it probably doesn't matter which error we're exclusively reporting.

I also kinda feel that this RichError::new construction should be replaced with a dedicated constructor on RichError. And "unknown reason" should probably be "empty parse".

Anyway these are just nits, you can ignore them.

src/parse.rs Outdated
(Token::LBrace, Token::RBrace),
(Token::LAngle, Token::RAngle),
],
move |_| fallback.clone(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 1ae8b0d:

This clone seems totally unnecessary; we can just move the fallback into this closure. Better, we could take the closure as an argument, and that would avoid executing the .clone() calls used to construct this fallback on almost every single call to delimited_with_recovery.

I: ValueInput<'tokens, Token = Token<'src>, Span = Span>,
{
select! {
Token::Ident(ident) => Self::from_str_unchecked(ident)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 1ae8b0d:

Is this single-option select! construction really idiomatic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, because select! is essentially a wrapper around a filter with a match statement, which also handles error reporting. I'm not a fan of using it either, but the alternative forces us to manually write the filtering and error reporting (which are done just fine by this macro).

src/error.rs Outdated
}
}

impl From<ErrorCollector> for String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 156da82:

I really don't like this. It means that you can use ? and silently convert a real error into a string. When we go to clean this up it'll be a big mess finding all the call sites and estimating how much work is left to done.

We should add a ErrorCollector::into_string method and then everywhere we're using ? to stringify this (in the next commit, 3670300 there are a few) we instead use .map_err(ErrorCollector::into_string)? which is much easier to grep for.

@apoelstra
Copy link
Contributor

Done reviewing 60bd05a. My comments are basically just nits and cleanups and I'm happy to deal with them later except I'd like to reduce the lockfile changes and I definitely want to get rid of the bad line-index dep.

@gerau gerau force-pushed the simc/chumsky-migration branch from 60bd05a to 04d3ae2 Compare February 4, 2026 13:34
@gerau
Copy link
Contributor Author

gerau commented Feb 4, 2026

In d9d2b1b:

There are tons of lockfile changes in this commit which have nothing to do with the chumsky addition.

Sorry about that, it seems that at some point I removed the Cargo.toml, and cargo generated a new one with updated versions.

gerau added 8 commits February 4, 2026 15:51
The lexer parses incoming code into tokens, which makes it simpler to
process using `chumsky`.
This commit introduce multiple changes, because it full rewrite of
parsing and error

Changes in `error.rs`:
- Change `Span` to use byte offsets in place of old `Position`
- Change `Display` function to calculate line and columns with inner
function
- Change `RichError` implementation to use new `Span` structure
- Implement `chumsky` error traits, so it can be used in error reporting
  of parsers
- add `expected..found` error
- remove unused `cmr` function for `Span` and unused error messages

Changes in `parse.rs`:
- Fully rewrite `pest` parsers to `chumsky` parsers.
- Change `ParseFromStr` trait to use this change.
This adds `ParseFromStrWithErrors`, which would take `ErrorCollector`
and return an `Option` of AST.
Also changes `TemplateProgram` to use new trait with collector
This adds tests to ensure that the compiler using the `chumsky` parser
produces the same Simplicity program as when using the `pest` parser for
the default examples.

The programs were compiled using an old `simc` version with debug
symbols into .json files, and located in `test-data/` folder.
We are no longer need this as we are no longer using the `pest` parser.
@gerau gerau force-pushed the simc/chumsky-migration branch from 04d3ae2 to 6026de1 Compare February 4, 2026 13:53
@gerau
Copy link
Contributor Author

gerau commented Feb 4, 2026

Fixed; also addressed nitpicks.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants