chumsky
chumsky copied to clipboard
Combine `then` and `or_not` preventing extraneous backtracking
I have parsers a
and b
and I want the following rules to work:
- if
a
succeeds, we try to parseb
and the combined parser returns an error fromb
orSome(result_tuple)
- if
a
fails, the combined parser returnsNone
If you just do a.then(b).or_not()
, which will have the desired return type, or_not
swallows both errors, but I need it to only swallow the error from a
.
An example where this is useful (ignore_then instead of then but the idea's the same):
// d(x) = just(x).padded()
let param = ident().then(d("=").ignore_then(simple_val).or_not());
let func = keyword("fn").padded().then(ident())
.then(param
.separated_by(d(","))
.collect()
.delimited_by(d("("), d(")")))
Now trying to parse fn test(a, b = bad_val)
you'll get an unhelpful error 'found = expected )', and it does make sense that it does that, but there should be a way to bubble up the error produced by the simple_val parser when we're gone past the =
.
I haven't found a good way of doing this short of trying to write my own primitive - is there such a way?.
If there was a flat_map of sorts (I vaguely remember something like that from scala combinators) where you could do
d("=").or_not().flat_map(|value| if value.is_some() { simple_val } else { noop } )
it'd be an okay-ish hack I guess.
Like the doc of the _ctx methods says Parse one thing and then another thing, creating the second parser from the result of the first.
, but it doesn't.. exactly do that?. There's this whole configure
thing instead.
Okay, since writing the above and leaving it in a tab I've found out custom
exists and wrote this
fn if_prefix<'a, I, E, X, O>(
prefix: impl Parser<'a, I, X, E> + Clone + 'a,
parser: impl Parser<'a, I, O, E> + Clone + 'a,
) -> impl Parser<'a, I, Option<O>, E>
where
I: Input<'a>,
E: ParserExtra<'a, I>,
{
custom(move |input| {
let marker = input.save();
Ok(match input.parse(prefix.clone()) {
Ok(_) => Some(input.parse(parser.clone())?),
Err(_) => {
input.rewind(marker);
None
}
})
})
}
which seems to work great.
Still posting this issue because I think this (or a bit more generalized version of this) is useful enough to be added to the lib.
Actually even in nanorust (if it had return types), if_prefix(token("->"), return_type)
would've been useful.
Also also, the same issue exists with separated_by:
parsing (a,a,a,bad)
gets us an error 'found , expected )', while not backtracking after a valid separator was parsed and pointing to the bad
token would've been better (perhaps having a config similar to how there's allow_trailing)
I need to look at this a bit deeper, but I think what you're asking for is just something like
a.then(b).or_not()
This will parse a
and b
, or neither.