Marwes / combine

A parser combinator library for Rust
https://docs.rs/combine/*/combine/
MIT License
1.29k stars 93 forks source link

choice! returns an error when one of its parsers would be successful #321

Open Ploppz opened 3 years ago

Ploppz commented 3 years ago

Reproducible test case:

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub enum ClusterType {
    Default,
    Dedicated,
}

#[derive(Debug, Eq, PartialEq)]
pub struct Cluster {
    pub pool_type: ClusterType,
    pub pool_project: Option<String>,
    pub n: usize,
}
    #[test]
    fn abc() {
        use combine::{
            choice, many, many1, none_of,
            parser::char::{digit, letter},
            struct_parser, value, Parser,
        };

        let integer = many1(digit()).map(|string: String| string.parse::<usize>().unwrap());
        let tag = |tag: &'static str| combine::tokens(|l, r| l == r, tag, tag.chars());
        let mut x_parser = struct_parser! {
            Cluster {
                _: tag("M "),
                _: many::<Vec<_>,_,_>(letter()),
                _: tag(" - "),
                n: integer.clone(),
                pool_type: value(ClusterType::Default),
                pool_project: value(None)
            }
        };
        let mut y_parser = struct_parser! {
            Cluster {
                _: tag("M "),
                _: many::<Vec<_>,_,_>(letter()),
                _: tag(" (project \""),
                pool_project: many(none_of("\"".chars())).map(Some),
                _: tag("\") - "),
                n: integer,
                pool_type: value(ClusterType::Dedicated)
            }
        };

        let x = "M Dev - 5";
        let y = "M Dev (project \"myproject\") - 4";
        println!("{:?}", x_parser.parse(x));
        println!("{:?}", y_parser.parse(x));
        println!("{:?}", x_parser.parse(y));
        println!("{:?}", y_parser.parse(y));

        let mut choice = choice!(x_parser, y_parser);

        println!("{:?}", choice.parse(x));
        println!("{:?}", choice.parse(y));
    }

Prints:

Ok((Cluster { pool_type: Default, pool_project: None, n: 5 }, ""))
Err(UnexpectedParse)
Err(UnexpectedParse)
Ok((Cluster { pool_type: Dedicated, pool_project: Some("myproject"), n: 4 }, ""))
Ok((Cluster { pool_type: Default, pool_project: None, n: 5 }, ""))
Err(UnexpectedParse)

I expected the last one to also be Ok because of how the y_parser can successfully parse y

Ploppz commented 3 years ago

Solved by @musikid: it needs to be choice!(attempt(x_parser), y_parser). I was looking at this documentation https://docs.rs/combine/4.6.0/combine/macro.choice.html which does not include any attempt - maybe a good idea to mention it there? Also why does that example work, seeing as in the first assert, the first parser will fail, but it still works without attempt()?

Marwes commented 3 years ago

That example works because it fails when looking at the first character, so it never consumes any input. It is thanks to this choice and similar parsers can produce error messages which hint to each alternative that was attempted. attempt makes a parser always work as if it consumed no input before it failed but it does this at the cost of making the error messages worse.

attempt ought to be linked in all parsers that tries alternatives, I actually thought it did already, maybe just in some of them.