From b6979b750c40a26ea1c665e064b17820cfd2c0eb Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Thu, 16 Feb 2017 16:06:07 -0800 Subject: [PATCH 1/4] Add map functions for Error<> and Info<> ranges. (#86) It's possible to `map_err_range` for `ParseResult<>` too, but it's awkward because the output type needs to be a compatible `StreamOnce`. As suggested in https://github.com/Marwes/combine/issues/86#issuecomment-280487165, it's probably best to either change the parse result type entirely, or wait for https://github.com/rust-lang/rust/issues/21903. This at least helps consumers convert `ParseError<>` into something that can implement `std::fmt::Display`. --- src/primitives.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/primitives.rs b/src/primitives.rs index 47acca32..c84df147 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -56,6 +56,18 @@ pub enum Info { Borrowed(&'static str), } +impl Info { + pub fn map_range(self, f: F) -> Info where F: FnOnce(R) -> S { + use self::Info::*; + match self { + Token(t) => Token(t), + Range(r) => Range(f(r)), + Owned(s) => Owned(s), + Borrowed(x) => Borrowed(x), + } + } +} + impl PartialEq for Info { fn eq(&self, other: &Info) -> bool { match (self, other) { @@ -110,6 +122,18 @@ pub enum Error { Other(Box), } +impl Error { + pub fn map_err_range(self, f: F) -> Error where F: FnOnce(R) -> S { + use self::Error::*; + match self { + Unexpected(x) => Unexpected(x.map_range(f)), + Expected(x) => Expected(x.map_range(f)), + Message(x) => Message(x.map_range(f)), + Other(x) => Other(x), + } + } +} + impl PartialEq for Error { fn eq(&self, other: &Error) -> bool { match (self, other) { From 3cd4951af2524236db0501afde33b6d87936ebdf Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Mon, 20 Feb 2017 12:07:03 -0800 Subject: [PATCH 2/4] Review comment: add `map_token`; s/map_err_range/map_range/. --- src/primitives.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/primitives.rs b/src/primitives.rs index c84df147..d2a0cd6a 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -57,6 +57,16 @@ pub enum Info { } impl Info { + pub fn map_token(self, f: F) -> Info where F: FnOnce(T) -> U { + use self::Info::*; + match self { + Token(t) => Token(f(t)), + Range(r) => Range(r), + Owned(s) => Owned(s), + Borrowed(x) => Borrowed(x), + } + } + pub fn map_range(self, f: F) -> Info where F: FnOnce(R) -> S { use self::Info::*; match self { @@ -123,7 +133,17 @@ pub enum Error { } impl Error { - pub fn map_err_range(self, f: F) -> Error where F: FnOnce(R) -> S { + pub fn map_token(self, f: F) -> Error where F: FnOnce(T) -> U { + use self::Error::*; + match self { + Unexpected(x) => Unexpected(x.map_token(f)), + Expected(x) => Expected(x.map_token(f)), + Message(x) => Message(x.map_token(f)), + Other(x) => Other(x), + } + } + + pub fn map_range(self, f: F) -> Error where F: FnOnce(R) -> S { use self::Error::*; match self { Unexpected(x) => Unexpected(x.map_range(f)), From d459495576a5d0ff7cdd988d4b41bcc37c799e4c Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Mon, 20 Feb 2017 13:55:19 -0800 Subject: [PATCH 3/4] Post: Test mapping ParseError to StdError without dropping internal details. --- src/lib.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 17eff6db..e17a732c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -483,4 +483,69 @@ mod tests { err }); } + + #[test] + fn extract_std_error() { + // The previous test verified that we could map a ParseError to a StdError by dropping the + // internal error details. This test verifies that we can map a ParseError to a StdError + // without dropping the internal error details. Consumers using `error-chain` will + // appreciate this. For technical reasons this is pretty janky; see the discussion in + // https://github.com/Marwes/combine/issues/86, and excuse the test with significant + // boilerplate! + use std::fmt; + use std::error::Error as StdError; + + #[derive(Clone, PartialEq, Debug)] + struct CloneOnly(String); + + #[derive(Debug)] + struct DisplayVec(Vec); + + #[derive(Debug)] + struct ExtractedError(usize, DisplayVec>>); + + impl std::error::Error for ExtractedError { + fn description(&self) -> &str { + "extracted error" + } + } + + impl fmt::Display for CloneOnly { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } + } + + impl fmt::Display for DisplayVec { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{:?}]", self.0) + } + } + + impl fmt::Display for ExtractedError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(writeln!(f, "Parse error at {}", self.0)); + Error::fmt_errors(&(self.1).0, f) + } + } + + let input = &[CloneOnly("x".to_string()), CloneOnly("y".to_string())][..]; + let result = token(CloneOnly("z".to_string())) + .parse(input) + .map_err(|e| e.translate_position(input)) + .map_err(|e| { + ExtractedError(e.position, DisplayVec(e.errors.into_iter().map(|e| e.map_range(|r| DisplayVec(r.to_owned()))).collect())) + }); + + assert!(result.is_err()); + // Test that the fresh ExtractedError is Display, so that the internal errors can be + // inspected by consuming code; and that the ExtractedError can be coerced to StdError. + let _ = result.map_err(|err| { + let s = format!("{}", err); + assert!(s.starts_with("Parse error at 0")); + assert!(s.contains("Expected")); + let err: Box = Box::new(err); + err + }); + } } From 9f77f43aa06d3440ec03c63881f49818aebbc73c Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Mon, 20 Feb 2017 23:09:49 +0100 Subject: [PATCH 4/4] fix: Make combine build on rust 1.11 --- src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e17a732c..db3df715 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -504,12 +504,12 @@ mod tests { #[derive(Debug)] struct ExtractedError(usize, DisplayVec>>); - impl std::error::Error for ExtractedError { + impl StdError for ExtractedError { fn description(&self) -> &str { "extracted error" } } - + impl fmt::Display for CloneOnly { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) @@ -534,7 +534,11 @@ mod tests { .parse(input) .map_err(|e| e.translate_position(input)) .map_err(|e| { - ExtractedError(e.position, DisplayVec(e.errors.into_iter().map(|e| e.map_range(|r| DisplayVec(r.to_owned()))).collect())) + ExtractedError(e.position, + DisplayVec(e.errors + .into_iter() + .map(|e| e.map_range(|r| DisplayVec(r.to_owned()))) + .collect())) }); assert!(result.is_err());