Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combine then and or_not preventing extraneous backtracking #630

Open
necauqua opened this issue Apr 30, 2024 · 1 comment
Open

Combine then and or_not preventing extraneous backtracking #630

necauqua opened this issue Apr 30, 2024 · 1 comment

Comments

@necauqua
Copy link

I have parsers a and b and I want the following rules to work:

  • if a succeeds, we try to parse b and the combined parser returns an error from b or Some(result_tuple)
  • if a fails, the combined parser returns None

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)

@zesterer
Copy link
Owner

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.

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

No branches or pull requests

2 participants