Module winnow::_tutorial::chapter_3

source ·
Available on crate feature unstable-doc only.
Expand description

§Chapter 3: Sequencing and Alternatives

In the last chapter, we saw how to create simple parsers using prebuilt parsers.

In this chapter, we explore two other widely used features: alternatives and composition.

§Sequencing

Now that we can create more interesting parsers, we can sequence them together, like:

fn parse_prefix<'s>(input: &mut &'s str) -> PResult<&'s str> {
    "0x".parse_next(input)
}

fn parse_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
    take_while(1.., (
        ('0'..='9'),
        ('A'..='F'),
        ('a'..='f'),
    )).parse_next(input)
}

fn main()  {
    let mut input = "0x1a2b Hello";

    let prefix = parse_prefix.parse_next(&mut input).unwrap();
    let digits = parse_digits.parse_next(&mut input).unwrap();

    assert_eq!(prefix, "0x");
    assert_eq!(digits, "1a2b");
    assert_eq!(input, " Hello");
}

To sequence these together, you can just put them in a tuple:

//...

fn main()  {
    let mut input = "0x1a2b Hello";

    let (prefix, digits) = (
        parse_prefix,
        parse_digits
    ).parse_next(&mut input).unwrap();

    assert_eq!(prefix, "0x");
    assert_eq!(digits, "1a2b");
    assert_eq!(input, " Hello");
}

Frequently, you won’t care about the literal and you can instead use one of the provided combinators, like preceded:

use winnow::combinator::preceded;

//...

fn main() {
    let mut input = "0x1a2b Hello";

    let digits = preceded(
        parse_prefix,
        parse_digits
    ).parse_next(&mut input).unwrap();

    assert_eq!(digits, "1a2b");
    assert_eq!(input, " Hello");
}

See combinator for more sequencing parsers.

§Alternatives

Sometimes, we might want to choose between two parsers; and we’re happy with either being used.

Stream::checkpoint helps us to retry parsing:

use winnow::stream::Stream;

fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
    let start = input.checkpoint();

    if let Ok(output) = ("0b", parse_bin_digits).parse_next(input) {
        return Ok(output);
    }

    input.reset(&start);
    if let Ok(output) = ("0o", parse_oct_digits).parse_next(input) {
        return Ok(output);
    }

    input.reset(&start);
    if let Ok(output) = ("0d", parse_dec_digits).parse_next(input) {
        return Ok(output);
    }

    input.reset(&start);
    ("0x", parse_hex_digits).parse_next(input)
}

// ...

fn main() {
    let mut input = "0x1a2b Hello";

    let (prefix, digits) = parse_digits.parse_next(&mut input).unwrap();

    assert_eq!(input, " Hello");
    assert_eq!(prefix, "0x");
    assert_eq!(digits, "1a2b");

    assert!(parse_digits(&mut "ghiWorld").is_err());
}

Warning: the above example is for illustrative purposes and relying on Result::Ok or Result::Err can lead to incorrect behavior. This will be clarified in later when covering error handling

opt is a basic building block for correctly handling retrying parsing:

use winnow::combinator::opt;

fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
    if let Some(output) = opt(("0b", parse_bin_digits)).parse_next(input)? {
        Ok(output)
    } else if let Some(output) = opt(("0o", parse_oct_digits)).parse_next(input)? {
        Ok(output)
    } else if let Some(output) = opt(("0d", parse_dec_digits)).parse_next(input)? {
        Ok(output)
    } else {
        ("0x", parse_hex_digits).parse_next(input)
    }
}

alt encapsulates this if/else-if ladder pattern, with the last case being the else:

use winnow::combinator::alt;

fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> {
    alt((
        ("0b", parse_bin_digits),
        ("0o", parse_oct_digits),
        ("0d", parse_dec_digits),
        ("0x", parse_hex_digits),
    )).parse_next(input)
}

Note: empty and fail are parsers that might be useful in the else case.

Sometimes a giant if/else-if ladder can be slow and you’d rather have a match statement for branches of your parser that have unique prefixes. In this case, you can use the dispatch macro:

use winnow::combinator::dispatch;
use winnow::token::take;
use winnow::combinator::fail;

fn parse_digits<'s>(input: &mut &'s str) -> PResult<&'s str> {
    dispatch!(take(2usize);
        "0b" => parse_bin_digits,
        "0o" => parse_oct_digits,
        "0d" => parse_dec_digits,
        "0x" => parse_hex_digits,
        _ => fail,
    ).parse_next(input)
}

// ...

fn main() {
    let mut input = "0x1a2b Hello";

    let digits = parse_digits.parse_next(&mut input).unwrap();

    assert_eq!(input, " Hello");
    assert_eq!(digits, "1a2b");

    assert!(parse_digits(&mut "ghiWorld").is_err());
}

Note: peek may be useful when dispatching from hints from each case’s parser.

See combinator for more alternative parsers.

Re-exports§