From 6658bb238d87f2182f3c2a6119b86c5fdea9b36b Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Thu, 1 Nov 2018 04:26:52 +0100 Subject: parser: Propagate errors to the root parser Thanks to Arnavion on Mozilla#rust for clueing me into this: > The documentation starts with "A parser in this library can be > described as a function which takes some input and if it is > successful, returns a value together with the remaining input." > Key phrase being "with the remaining input" > So I assume it just matches as best as it can (which is nothing, in > your case) > Maybe you want to use some one-or-more combinator rather than many > which is a zero-or-more combinator > Then add your own check that "the remaining input" is empty Replacing my `many()` parsers with `many1()` correctly propagates errors up from sub-parers. Nice! This will fail when we try to parse an empty string, but we can just add a special case for that to parse successfully. --- src/parser.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 7afce9d..c7bccc0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -574,7 +574,7 @@ where I: Stream, I::Error: ParseError, { - many(map().skip(blank())) + many1(map().skip(blank())) } fn map_collection() -> impl Parser @@ -631,7 +631,7 @@ where { ( blank(), - many( + many1( choice!( try(map()).map(|map| Definition::Map(map)), try(mode()).map(|mode| Definition::Mode(mode)) @@ -1132,6 +1132,35 @@ cmd echo test assert_eq!(result, Ok(expected)); } + #[test] + fn map_collection_fails_without_terminating_newline() { + let text = "map works +map fails"; + let result = map_collection().easy_parse(State::new(text)).map(|t| t.0); + + let mut expected = HashMap::new(); + expected.insert( + vec![HeadphoneButton::Play], + MapAction { + action: Action::String("works".to_owned()), + kind: MapKind::Map, + }, + ); + + assert_eq!(result, Err(easy::Errors { + position: ::combine::stream::state::SourcePosition { + line: 2, + column: 17, + }, + errors: vec![ + easy::Error::Unexpected("end of input".into()), + easy::Error::Unexpected('f'.into()), + easy::Error::Expected("whitespace".into()), + easy::Error::Expected("tab".into()) + ], + })); + } + #[test] fn map_collection_parses_maps() { let text = " @@ -1330,4 +1359,28 @@ cmd /usr/bin/say hello assert_eq!(result, Ok(expected)); } + + #[test] + fn map_group_with_invalid_input_fails() { + let text = "not-a-kind +"; + let result = map_group().easy_parse(State::new(text)).map(|t| t.0); + + assert_eq!(result, Err(easy::Errors { + position: ::combine::stream::state::SourcePosition { + line: 1, + column: 1, + }, + errors: vec![ + easy::Error::Unexpected('n'.into()), + easy::Error::Expected("map".into()), + easy::Error::Expected("cmd".into()), + easy::Error::Expected("mode".into()), + easy::Error::Expected("lf newline".into()), + easy::Error::Expected("whitespace".into()), + easy::Error::Expected("tab".into()), + easy::Error::Expected('#'.into()), + ], + })); + } } -- cgit v1.2.3