mirror of
https://github.com/zaphar/abortable_parser.git
synced 2025-07-21 20:29:49 -04:00
FEATURE: Error Wrapping as a first class citizen.
This commit is contained in:
parent
00bd55874c
commit
d24df3b852
@ -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
|
||||||
}
|
}
|
||||||
@ -48,4 +48,6 @@ impl<'a, T: Debug + 'a> Clone for SliceIter<'a, T> {
|
|||||||
offset: self.offset,
|
offset: self.offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Debug + 'a> InputIter for SliceIter<'a, T> {}
|
75
src/lib.rs
75
src/lib.rs
@ -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 {
|
||||||
|
@ -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)*))
|
||||||
|
34
src/test.rs
34
src/test.rs
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user