FEATURE: Error Wrapping as a first class citizen.

This commit is contained in:
Jeremy Wall 2018-09-03 00:05:32 -05:00
parent 00bd55874c
commit d24df3b852
4 changed files with 124 additions and 18 deletions

View File

@ -2,7 +2,7 @@
use std::iter::Iterator; use std::iter::Iterator;
use std::fmt::Debug; use std::fmt::Debug;
use super::InputIter; use super::{Offsetable, InputIter};
/// Implements `InputIter` for any slice of T. /// Implements `InputIter` for any slice of T.
#[derive(Debug)] #[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 { fn get_offset(&self) -> usize {
self.offset self.offset
} }
@ -49,3 +49,5 @@ impl<'a, T: Debug + 'a> Clone for SliceIter<'a, T> {
} }
} }
} }
impl<'a, T: Debug + 'a> InputIter for SliceIter<'a, T> {}

View File

@ -1,26 +1,89 @@
//! A parser combinator library with a focus on fully abortable parsing and error handling. //! A parser combinator library with a focus on fully abortable parsing and error handling.
use std::iter::Iterator; 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. /// A Cloneable Iterator that can report an offset as a count of processed Items.
pub trait InputIter: Iterator + Clone { pub trait InputIter: Iterator + Clone + Offsetable {}
fn get_offset(&self) -> usize;
#[derive(Debug)]
pub struct Error<E: Display> {
err: E,
offset: usize,
cause: Option<Box<Error<E>>>,
}
impl<E: Display> Error<E> {
// Constructs a new Error with an offset and no cause.
pub fn new<S: Offsetable>(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<S: Offsetable>(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<E>> {
match self.cause {
Some(ref cause) => Some(cause),
None => None,
}
}
pub fn get_offset(&self) -> usize {
self.offset
}
}
impl<E: Display> Display for Error<E> {
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. /// The result of a parsing attempt.
#[derive(Debug)] #[derive(Debug)]
pub enum Result<I: InputIter, O, E> { pub enum Result<I: InputIter, O, E: Display> {
/// Complete represents a successful match. /// Complete represents a successful match.
Complete(I, O), Complete(I, O),
/// Incomplete indicates input ended before a match could be completed. /// 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. /// It contains the offset at which the input ended before a match could be completed.
Incomplete(usize), Incomplete(usize),
/// Fail represents a failed match. /// Fail represents a failed match.
Fail(E), Fail(Error<E>),
/// Abort represents a match failure that the parser cannot recover from. /// Abort represents a match failure that the parser cannot recover from.
Abort(E), Abort(Error<E>),
} }
impl<I: InputIter, O, E> Result<I, O, E> { impl<I: InputIter, O, E: Display> Result<I, O, E> {
/// Returns true if the Result is Complete. /// Returns true if the Result is Complete.
pub fn is_complete(&self) -> bool { pub fn is_complete(&self) -> bool {
if let &Result::Complete(_, _) = self { if let &Result::Complete(_, _) = self {

View File

@ -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. /// Turns Aborts into fails allowing you to trap and then convert an Abort into a normal Fail.
#[macro_export] #[macro_export]
macro_rules! trap { macro_rules! trap {
@ -46,14 +63,15 @@ macro_rules! trap {
/// to construct the errors for the Incomplete case. /// to construct the errors for the Incomplete case.
#[macro_export] #[macro_export]
macro_rules! must_complete { 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)*) { match $f!($i, $($args)*) {
$crate::Result::Complete(i, o) => $crate::Result::Complete(i, o), $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::Fail(e) => $crate::Result::Abort(e),
$crate::Result::Abort(e) => $crate::Result::Abort(e), $crate::Result::Abort(e) => $crate::Result::Abort(e),
} }
}; }};
($i:expr, $efn:expr, $f:ident) => { ($i:expr, $efn:expr, $f:ident) => {
must_complete!($i, $efn, run!($f)) must_complete!($i, $efn, run!($f))
@ -107,7 +125,6 @@ macro_rules! do_each {
do_each!($i, _ => run!($f), $( $rest )* ) do_each!($i, _ => run!($f), $( $rest )* )
}; };
// FIXME(jwall): Make this internal only.
// Our Terminal condition // Our Terminal condition
($i:expr, ( $($rest:tt)* ) ) => { ($i:expr, ( $($rest:tt)* ) ) => {
Result::Complete($i, ($($rest)*)) Result::Complete($i, ($($rest)*))

View File

@ -1,5 +1,5 @@
use super::iter::SliceIter; use super::iter::SliceIter;
use super::{Result, InputIter}; use super::{Result, Offsetable};
#[test] #[test]
fn test_slice_iter() { fn test_slice_iter() {
@ -31,8 +31,8 @@ fn test_slice_iter() {
assert_eq!('o' as u8, out[2]); assert_eq!('o' as u8, out[2]);
} }
fn will_fail(_: SliceIter<u8>) -> Result<SliceIter<u8>, String, String> { fn will_fail(i: SliceIter<u8>) -> Result<SliceIter<u8>, String, String> {
Result::Fail("AAAAHHH!!!".to_string()) Result::Fail(super::Error::new("AAAAHHH!!!".to_string(), &i))
} }
fn parse_byte(mut i: SliceIter<u8>) -> Result<SliceIter<u8>, u8, String> { fn parse_byte(mut i: SliceIter<u8>) -> Result<SliceIter<u8>, u8, String> {
@ -66,6 +66,30 @@ fn parse_three(i: SliceIter<u8>) -> Result<SliceIter<u8>, 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] #[test]
fn test_must_fails() { fn test_must_fails() {
let input_str = "foo"; let input_str = "foo";
@ -119,9 +143,9 @@ fn test_must_complete() {
let input_str = "foo"; let input_str = "foo";
let iter = SliceIter::new(input_str.as_bytes()); let iter = SliceIter::new(input_str.as_bytes());
let iter_fail = iter.clone(); 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()); 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()); assert!(result.is_abort());
} }