Add a combinator `then_or_not` to optionally run a parser after another
Closes #630, which first mentioned something like this.
This can be used in syntaxes like Rust's let statement, which has an optional type notation:
let a = 0;
let b: i32 = 0;
.then(...).or_not() doesn't work well in such cases, because when there's a parse error in the type, or_not would just suppress it and return None, leaving the following parsers confusing, or even worse, mistakenly starting parsing from the wrong position. In this particular example, when the type has an error, the parser may complain something like expected '=', got ':', rather than pointing out the error in the type.
With then_or_not, Rust's let statement can roughly be parsed with:
let let_stmt = group((
just("let").padded(),
pattern,
just(':').padded().then_or_not(rust_type),
just('=').padded().then_or_not(expr),
))
.map(|(_, pat, ty, val)| Stmt::Let(pat, ty.map(|t| t.1), val.map(|v| v.1)));
And this would correctly report the errors in rust_type and expr.
.then(...).or_not() doesn't work well in such cases, because when there's a parse error in the type, or_not would just suppress it and return None, leaving the following parsers confusing, or even worse, mistakenly starting parsing from the wrong position
I'm not sure I follow this logic. Optional type annotations can be expressed perfectly well (I've been doing so in my own lang for years) with the following
let pat = ... ; // Pattern
let ty = ... ;// Type annotation
text::keyword("let").padded()
.ignore_then(pat)
.then(just(":").padded().ignore_then(ty).or_not())
.then_ignore(just("=").padded())
.then(...)
Chumsky doesn't get confused by this, and the fact that the error is suppressed when parsing fails is not a problem: chumsky will still remember that the error could have been hit and will make use of that information when generating the final syntax error.
Is this still an API hole for you, or is my suggested solution sufficient?