From d24df3b852db305902cac032ce0f37315446ff74 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Mon, 3 Sep 2018 00:05:32 -0500 Subject: [PATCH] FEATURE: Error Wrapping as a first class citizen. --- src/iter.rs | 8 +++--- src/lib.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++----- src/macros.rs | 25 ++++++++++++++--- src/test.rs | 34 +++++++++++++++++++---- 4 files changed, 124 insertions(+), 18 deletions(-) diff --git a/src/iter.rs b/src/iter.rs index 50af31a..b8eac17 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -2,7 +2,7 @@ use std::iter::Iterator; use std::fmt::Debug; -use super::InputIter; +use super::{Offsetable, InputIter}; /// Implements `InputIter` for any slice of T. #[derive(Debug)] @@ -35,7 +35,7 @@ impl<'a, T: Debug + 'a> Iterator for SliceIter<'a, T> { } } -impl<'a, T: Debug + 'a> InputIter for SliceIter<'a, T> { +impl<'a, T: Debug + 'a> Offsetable for SliceIter<'a, T> { fn get_offset(&self) -> usize { self.offset } @@ -48,4 +48,6 @@ impl<'a, T: Debug + 'a> Clone for SliceIter<'a, T> { offset: self.offset, } } -} \ No newline at end of file +} + +impl<'a, T: Debug + 'a> InputIter for SliceIter<'a, T> {} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ff73e1f..c6cd520 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,26 +1,89 @@ //! A parser combinator library with a focus on fully abortable parsing and error handling. use std::iter::Iterator; +use std::fmt::Display; + +pub trait Offsetable { + fn get_offset(&self) -> usize; +} + +impl Offsetable for usize { + fn get_offset(&self) -> usize { + return *self; + } +} /// A Cloneable Iterator that can report an offset as a count of processed Items. -pub trait InputIter: Iterator + Clone { - fn get_offset(&self) -> usize; +pub trait InputIter: Iterator + Clone + Offsetable {} + +#[derive(Debug)] +pub struct Error { + err: E, + offset: usize, + cause: Option>>, +} + +impl Error { + // Constructs a new Error with an offset and no cause. + pub fn new(err: E, offset: &S) -> Self { + Error{ + err: err, + offset: offset.get_offset(), + cause: None, + } + } + + // Constructs a new Error with an offset and a cause. + pub fn caused_by(err: E, offset: &S, cause: Self) -> Self { + Error{ + err: err, + offset: offset.get_offset(), + cause: Some(Box::new(cause)), + } + } + + pub fn get_err<'a>(&'a self) -> &'a E { + &self.err + } + + pub fn get_cause<'a>(&'a self) -> Option<&'a Error> { + match self.cause { + Some(ref cause) => Some(cause), + None => None, + } + } + + pub fn get_offset(&self) -> usize { + self.offset + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { + try!(write!(f, "{}", self.err)); + match self.cause { + Some(ref c) => write!(f, "\n\tCaused By:{}", c), + None => { + Ok(()) + }, + } + } } /// The result of a parsing attempt. #[derive(Debug)] -pub enum Result { +pub enum Result { /// Complete represents a successful match. Complete(I, O), /// Incomplete indicates input ended before a match could be completed. /// It contains the offset at which the input ended before a match could be completed. Incomplete(usize), /// Fail represents a failed match. - Fail(E), + Fail(Error), /// Abort represents a match failure that the parser cannot recover from. - Abort(E), + Abort(Error), } -impl Result { +impl Result { /// Returns true if the Result is Complete. pub fn is_complete(&self) -> bool { if let &Result::Complete(_, _) = self { diff --git a/src/macros.rs b/src/macros.rs index 83e0ee5..08488e5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -25,6 +25,23 @@ macro_rules! must { }; } +#[macro_export] +macro_rules! wrap_err { + ($i:expr, $f:ident!( $( $args:tt )* ), $e:expr) => {{ + let _i = $i.clone(); + match $f!($i, $($args)*) { + $crate::Result::Complete(i, o) => $crate::Result::Complete(i, o), + $crate::Result::Incomplete(offset) => $crate::Result::Incomplete(offset), + $crate::Result::Fail(e) => $crate::Result::Fail($crate::Error::caused_by($e, &_i, e)), + $crate::Result::Abort(e) => $crate::Result::Abort($crate::Error::caused_by($e, &_i, e)), + } + }}; + + ($i:expr, $f:ident, $e:expr) => { + wrap_err!($i, run!($f), $e) + }; +} + /// Turns Aborts into fails allowing you to trap and then convert an Abort into a normal Fail. #[macro_export] macro_rules! trap { @@ -46,14 +63,15 @@ macro_rules! trap { /// to construct the errors for the Incomplete case. #[macro_export] macro_rules! must_complete { - ($i:expr, $efn:expr, $f:ident!( $( $args:tt )* ) ) => { + ($i:expr, $e:expr, $f:ident!( $( $args:tt )* ) ) => {{ + let _i = $i.clone(); match $f!($i, $($args)*) { $crate::Result::Complete(i, o) => $crate::Result::Complete(i, o), - $crate::Result::Incomplete(offset) => $crate::Result::Abort($efn(offset)), + $crate::Result::Incomplete(ref offset) => $crate::Result::Abort($crate::Error::new($e, offset)), $crate::Result::Fail(e) => $crate::Result::Abort(e), $crate::Result::Abort(e) => $crate::Result::Abort(e), } - }; + }}; ($i:expr, $efn:expr, $f:ident) => { must_complete!($i, $efn, run!($f)) @@ -107,7 +125,6 @@ macro_rules! do_each { do_each!($i, _ => run!($f), $( $rest )* ) }; - // FIXME(jwall): Make this internal only. // Our Terminal condition ($i:expr, ( $($rest:tt)* ) ) => { Result::Complete($i, ($($rest)*)) diff --git a/src/test.rs b/src/test.rs index b6e3d1f..6b9f567 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,5 +1,5 @@ use super::iter::SliceIter; -use super::{Result, InputIter}; +use super::{Result, Offsetable}; #[test] fn test_slice_iter() { @@ -31,8 +31,8 @@ fn test_slice_iter() { assert_eq!('o' as u8, out[2]); } -fn will_fail(_: SliceIter) -> Result, String, String> { - Result::Fail("AAAAHHH!!!".to_string()) +fn will_fail(i: SliceIter) -> Result, String, String> { + Result::Fail(super::Error::new("AAAAHHH!!!".to_string(), &i)) } fn parse_byte(mut i: SliceIter) -> Result, u8, String> { @@ -66,6 +66,30 @@ fn parse_three(i: SliceIter) -> Result, String, String> { } } +#[test] +fn test_wrap_err_fail() { + let input_str = "foo"; + let iter = SliceIter::new(input_str.as_bytes()); + let result = wrap_err!(iter, will_fail, "haha!".to_string()); + assert!(result.is_fail()); + if let Result::Fail(e) = result { + assert!(e.get_cause().is_some()); + assert_eq!("AAAAHHH!!!", e.get_cause().unwrap().get_err()); + } +} + +#[test] +fn test_wrap_err_abort() { + let input_str = "foo"; + let iter = SliceIter::new(input_str.as_bytes()); + let result = wrap_err!(iter, must!(will_fail), "haha!".to_string()); + assert!(result.is_abort()); + if let Result::Abort(e) = result { + assert!(e.get_cause().is_some()); + assert_eq!("AAAAHHH!!!", e.get_cause().unwrap().get_err()); + } +} + #[test] fn test_must_fails() { let input_str = "foo"; @@ -119,9 +143,9 @@ fn test_must_complete() { let input_str = "foo"; let iter = SliceIter::new(input_str.as_bytes()); let iter_fail = iter.clone(); - let mut result = must_complete!(iter, |_| "AHHH".to_string(), will_not_complete); + let mut result = must_complete!(iter, "AHHH".to_string(), will_not_complete); assert!(result.is_abort()); - result = must_complete!(iter_fail, |_| "AHHH".to_string(), will_fail); + result = must_complete!(iter_fail, "AHHH".to_string(), will_fail); assert!(result.is_abort()); }