combine
combine copied to clipboard
choice! returns an error when one of its parsers would be successful
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
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()
?
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.