rust-bakery / nom

Rust parser combinator framework
MIT License
9.38k stars 804 forks source link

Implements combinator `flat_some`. #1768

Closed zadlg closed 1 day ago

zadlg commented 3 months ago

Hello nom team.

This is my very first contribution to nom, so I maybe did things wrong here. Also, please note that I'm quite bad at naming things, so flat_some might not be the most meaningful name!

I'm writing a parser using nom (which is a great library btw), and I came across the following pattern which I found verbose to express with nom, especially because I'm using it a lot:

use nom::combinator;
use nom::IResult;
use nom::bytes::complete as bytes;

fn parse_if_some<'a>(s: Option<&'a str>) -> impl Fn(&'a str) -> IResult<&'a str, Option<&'a str>> {
  move |i: &str| if s.is_some() {
      combinator::map(
          bytes::tag("next"),
          Some,
      )(i)
  } else {
    combinator::success(None)(i)
  }
}

fn parser(s: &str) -> IResult<&str, Option<&str>> {
    combinator::flat_map(
      combinator::opt(bytes::tag("first")),
      parse_if_some,
    )(s)
}

fn main() {
    assert_eq!(parser("firstnext"), Ok(("", Some("next"))));
    assert!(parser("firstinvalid").is_err());
}

The idea is that if a first parser succeed, then we discard its result, and apply a second one on the following input. It is close to sequence::preceded, but if the first parser failed, then we return None, otherwise it returns either an Error or an Option<O>.

I'm using it over combinator::opt because I want to make sure that if I recognize a first pattern, then the following input must be correct. But if I don't recognize that first pattern, then it's fine and we can move on.

Maybe there are other "simpler" ways to do it without implementing a new combinator, and in this case I'm fine with discarding this PR. :)