Skip to content

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

}

#[test]
#[ignore]
Copy link
Collaborator

Choose a reason for hiding this comment

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

1b1e751 It's nice to see that chumsky seems to be faster than pest here.

@gerau gerau force-pushed the simc/chumsky-migration branch from 1b1e751 to 1e7c61b Compare January 14, 2026 15:10
src/error.rs Outdated
})
.map_or(0, |ts| u32::from(ts) as usize);

let start_col = file[line_start_byte..self.span.start].chars().count();
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to count columns as being the number of utf8 codepoints? There's no good way to define "number of columns" in general for non-ascii text, but LSP defines it as the number of utf16 codepoints and that's the closest thing to a standard that I'm aware of.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually I just checked and LSP now allows you to choose between utf{8,16,32} at your leisure. But it's moot anyway since this is just deciding how long an underline to print and that's going to depend on the terminal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should consider switching to ariadne for error pretty-printing, as it's the "sister-crate" for chumsky.

@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

Comment on lines +234 to +190
let start_pos = index.line_col(TextSize::from(self.span.start as u32));
let end_pos = index.line_col(TextSize::from(self.span.end as u32));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we have From trait implemented to avoid repeating the TextSize::from(self.span.start as u32)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Span has a start and an end, so the only way to implement the From trait would be for a tuple of TextSizes. That isn't a very pretty solution either, so I'd prefer to stick with the current variant.

@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
src/lexer.rs Outdated
Comment on lines 128 to 130
"true" => Token::Bool(true),
"false" => Token::Bool(false),
"bool" => Token::BooleanType,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hardcoded built-in names - "true", "false", "bool" are treated as keywords at the lexer level

Let's create a GitHub issue, so we do not forget to resolve this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it's a problem for "true" or "false," because the Rust compiler doesn't allow variables to be named "true" or "false".

However, I can change it for "bool" very quickly, as it wouldn't be so difficult.

Comment on lines +118 to +122
let macros =
choice((just("assert!"), just("panic!"), just("dbg!"), just("list!"))).map(Token::Macro);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Macros are lexed as special Macro tokens, therefore adding new macros would require lexer changes, let's mention it in the issue as well

Maybe we could consider treating ident! as a general pattern

// We would discard them for the compiler, but they are needed, for example, for the formatter.
Comment,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you create an issue "Formatter support" and mention that content is discarded for now?

/// ## Errors
///
/// The string is not a valid SimplicityHL program.
pub fn new<Str: Into<Arc<str>>>(s: Str) -> Result<Self, String> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why we cannot return Error type instead of String?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's because at this stage we only want to have pretty-printed diagnostics?

Comment on lines 48 to 54
impl TemplateProgram {
/// Parse the template of a SimplicityHL program.
///
/// ## Errors
///
/// The string is not a valid SimplicityHL program.
pub fn new<Str: Into<Arc<str>>>(s: Str) -> Result<Self, String> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

TemplateProgram::new() collects errors but only returns them as single string, so are multiple errors included into single string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you are correct.

@@ -664,4 +675,167 @@
.with_witness_values(WitnessValues::default())
.assert_run_success();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we could also add test that checks that multiple errors are detected?

Comment on lines +72 to +79
let compiled =
match CompiledProgram::new(prog_text, Arguments::default(), include_debug_symbols) {
Ok(program) => program,
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we could use approach like below?

use std::process::{ExitCode, Termination};

  struct CliError(String);

  impl Termination for CliError {
      fn report(self) -> ExitCode {
          eprintln!("{}", self.0);
          ExitCode::from(1)
      }
  }

  fn main() -> Result<(), CliError> {
      // ...
      let compiled = CompiledProgram::new(prog_text, Arguments::default(), include_debug_symbols)
          .map_err(CliError)?;
      // ...
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I implemented this code because I was having some trouble with the error display -- it was showing up like a Debug print, and CliError doesn't solve that issue. I'd also like to avoid touching the CLI too much right now, as this PR is mainly focused on the parser changes.

src/parse.rs Outdated
Comment on lines 911 to 894
tok.into_iter()
.map(|(tok, span)| (tok, Span::from(span)))
.filter(|(tok, _)| !matches!(tok, Token::Comment | Token::BlockComment))
.collect::<Vec<_>>()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Duplication with ParseFromStrWithErrors

span: Span::DUMMY,
})
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

No unit tests is quite troublesome, though this file is already huge

@gerau gerau force-pushed the simc/chumsky-migration branch from 4bf6252 to 6a5bc25 Compare January 29, 2026 16:12
The lexer parses incoming code into tokens, which makes it simpler to
process using `chumsky`.
@gerau gerau force-pushed the simc/chumsky-migration branch from 6a5bc25 to 7e5a257 Compare January 30, 2026 13:07
@gerau gerau mentioned this pull request Jan 30, 2026
gerau added 5 commits January 30, 2026 16:17
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`
- Add `line-index` crate to calculate line and column of byte offset
- 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

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.
@gerau gerau force-pushed the simc/chumsky-migration branch from 7e5a257 to e53f005 Compare January 30, 2026 14:21
@gerau gerau marked this pull request as ready for review January 30, 2026 14:29
@gerau gerau requested a review from delta1 as a code owner January 30, 2026 14:29
@gerau
Copy link
Contributor Author

gerau commented Jan 30, 2026

cc @apoelstra

This was referenced Jan 30, 2026
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