From ed908e7c139849c012e80f2862fac53b48b93d90 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Fri, 7 Sep 2018 22:09:45 -0500 Subject: [PATCH] REFACTOR: Combinator functions and no more matchers module. We also made the Error struct take string messages instead of arbitary displayable types. --- src/combinators.rs | 277 +++++++++++++++++++++++++++++++++++++-------- src/lib.rs | 43 +++---- src/matchers.rs | 116 ------------------- src/test.rs | 10 +- 4 files changed, 258 insertions(+), 188 deletions(-) delete mode 100644 src/matchers.rs diff --git a/src/combinators.rs b/src/combinators.rs index 08d2644..9d4a16e 100644 --- a/src/combinators.rs +++ b/src/combinators.rs @@ -1,5 +1,26 @@ //! Contains combinators that can assemble other matchers or combinators into more complex grammars. +use super::{InputIter, Error, Result}; + +/// Turns a `Result` to it's inverse. +/// +/// `Result::Fail` becomes `Result::Complete` and `Result::Complete` becomes `Result::Fail`. +/// You must pass in an iterator at the appropriate spot for the next combinator +/// to start at. +/// +/// The `not!` macro provides syntactic sugar for using this combinator properly. +pub fn not(i: I, result: Result) -> Result +where + I: InputIter, +{ + match result { + Result::Complete(i, _) => Result::Fail(Error::new("Matched on input when we shouldn't have.".to_string(), &i)), + Result::Abort(e) => Result::Abort(e), + Result::Incomplete(offset) => Result::Incomplete(offset), + Result::Fail(_) => Result::Complete(i, ()), + } +} + /// Turns a matcher into it's inverse, only succeeding if the the matcher returns a Fail. /// Does not consume it's input and only returns (). /// @@ -21,14 +42,8 @@ #[macro_export] macro_rules! not { ($i:expr, $f:ident!( $( $args:tt )* ) ) => {{ - use $crate::{Result, Error}; let _i = $i.clone(); - match trap!(_i, $f!($($args)*)) { - Result::Complete(i, _) => Result::Fail(Error::new("Matched on input when we shouldn't have.".to_string(), &i)), - Result::Abort(e) => Result::Abort(e), - Result::Incomplete(offset) => Result::Incomplete(offset), - Result::Fail(_) => Result::Complete($i, ()), - } + $crate::combinators::not(_i, trap!($i.clone(), $f!($($args)*))) }}; ($i:expr, $f:ident( $( $args:tt )* ) ) => { @@ -87,8 +102,27 @@ macro_rules! run { }; } -/// Turns Fails into Aborts. Allows you to turn any parse failure into a hard abort of -/// the parser. +/// Maps a `Result::Fail` to a `Result::Abort`. +/// +/// It leaves the rest of the Result variants untouched. +/// +/// The `must!` macro provided syntactice sugar for using this combinator. +pub fn must(result: Result) -> Result +where + I: InputIter +{ + match result { + Result::Complete(i, o) => Result::Complete(i, o), + Result::Incomplete(offset) => Result::Incomplete(offset), + Result::Fail(e) => Result::Abort(e), + Result::Abort(e) => Result::Abort(e), + } + +} + +/// Turns `Result::Fail` into `Result::Abort`. +/// +/// Allows you to turn any parse failure into a hard abort of the parser. /// /// ``` /// # #[macro_use] extern crate abortable_parser; @@ -109,12 +143,7 @@ macro_rules! run { #[macro_export] macro_rules! must { ($i:expr, $f:ident!( $( $args:tt )* ) ) => { - 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::Abort(e), - $crate::Result::Abort(e) => $crate::Result::Abort(e), - } + $crate::combinators::must($f!($i, $($args)*)) }; ($i:expr, $f:ident) => { @@ -145,8 +174,25 @@ macro_rules! wrap_err { }; } -/// Turns Aborts into fails allowing you to trap and then convert an Abort into a -/// normal Fail. +/// Traps a `Result::Abort` and converts it into a `Result::Fail`. +/// +/// This is the semantic inverse of `must`. +/// +/// The `trap!` macro provides syntactic sugar for using this combinator. +pub fn trap(result: Result) -> Result +where + I: InputIter +{ + match result { + Result::Complete(i, o) => Result::Complete(i, o), + Result::Incomplete(offset) => Result::Incomplete(offset), + Result::Fail(e) => Result::Fail(e), + Result::Abort(e) => Result::Fail(e), + } +} + +/// Turns `Result::Abort` into `Result::Fail` allowing you to trap and then convert any `Result::Abort` +/// into a normal Fail. /// /// ``` /// # #[macro_use] extern crate abortable_parser; @@ -162,12 +208,7 @@ macro_rules! wrap_err { #[macro_export] macro_rules! trap { ($i:expr, $f:ident!( $( $args:tt )* ) ) => { - 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(e), - $crate::Result::Abort(e) => $crate::Result::Fail(e), - } + $crate::combinators::trap($f!($i, $($args)*)) }; ($i:expr, $f:ident) => { @@ -175,8 +216,27 @@ macro_rules! trap { }; } -/// Turns Fails and Incompletes into Aborts. You must specify the error message -/// to use in case the matcher is Incomplete. +/// Turns `Result::Fail` or `Result::Incomplete` into `Result::Abort`. +/// +/// You must specify the error message to use in case the matcher is incomplete. +/// +/// The must_complete! macro provides syntactic sugar for using this combinator. +pub fn must_complete(result: Result, msg: M) -> Result +where + I: InputIter, + M: Into, +{ + match result { + Result::Complete(i, o) => Result::Complete(i, o), + Result::Incomplete(ref offset) => Result::Abort(Error::new(msg, offset)), + Result::Fail(e) => Result::Abort(e), + Result::Abort(e) => Result::Abort(e), + } +} + +/// Turns `Result::Fail` and `Result::Incomplete` into `Result::Abort`. +/// +/// You must specify the error message to use in case the matcher is incomplete. /// /// ``` /// # #[macro_use] extern crate abortable_parser; @@ -191,13 +251,7 @@ macro_rules! trap { #[macro_export] macro_rules! must_complete { ($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(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), - } + $crate::combinators::must_complete($f!($i, $($args)*), $e) }}; ($i:expr, $efn:expr, $f:ident) => { @@ -407,8 +461,37 @@ macro_rules! either { } } +/// Maps a `Result` to be optional. +/// +/// `Result::Fail` maps to None and `Result::Complete` maps to Some. The rest of the +/// `Result` variants are left untouched. You must pass in the iterator that the +/// next matcher should use in the event of a fail. +/// +/// The `optional!` macro provides some syntactice sugar for using this combinator +/// properly. +pub fn optional(iter: I, result: Result) -> Result> +where + I: InputIter, +{ + match result { + Result::Complete(i, o) => { + Result::Complete(i, Some(o)) + } + // Incomplete could still work possibly parse. + Result::Incomplete(i) => { + Result::Incomplete(i) + } + // Fail just means it didn't match. + Result::Fail(_) => { + Result::Complete(iter, None) + }, + // Aborts are hard failures that the parser can't recover from. + Result::Abort(e) => Result::Abort(e), + } +} + /// Treats a sub parser as optional. It returns Some(output) for a successful match -/// and None for Fails. +/// and None for failures. /// /// ``` /// # #[macro_use] extern crate abortable_parser; @@ -437,21 +520,7 @@ macro_rules! optional { (__impl $i:expr, $f:ident!( $( $args:tt )* )) => {{ let _i = $i.clone(); - match $f!($i, $($args)*) { - $crate::Result::Complete(i, o) => { - Result::Complete(i, Some(o)) - } - // Incomplete could still work possibly parse. - $crate::Result::Incomplete(i) => { - Result::Incomplete(i) - } - // Fail just means it didn't match. - $crate::Result::Fail(_) => { - Result::Complete(_i, None) - }, - // Aborts are hard failures that the parser can't recover from. - $crate::Result::Abort(e) => Result::Abort(e), - } + $crate::combinators::optional(_i, $f!($i, $($args)*)) }}; } @@ -514,3 +583,115 @@ macro_rules! repeat { repeat!($i, run!($f)) }; } + +/// Convenience macro for looking for a specific text token in a byte input stream. +/// +/// ``` +/// # #[macro_use] extern crate abortable_parser; +/// use abortable_parser::iter; +/// # use abortable_parser::{Result, Offsetable}; +/// use std::convert::From; +/// # fn main() { +/// let iter: iter::SliceIter = "foo bar".into(); +/// let tok = text_token!(iter, "foo"); +/// # assert!(tok.is_complete()); +/// if let Result::Complete(i, o) = tok { +/// assert_eq!(i.get_offset(), 3); +/// assert_eq!(o, "foo"); +/// } +/// # } +/// ``` +#[macro_export] +macro_rules! text_token { + ($i:expr, $e:expr) => {{ + use $crate::Error; + use $crate::Result; + let mut _i = $i.clone(); + let mut count = 0; + for expected in $e.bytes() { + let item = match _i.next() { + Some(item) => item, + None => break, + }; + if item == &expected { + count += 1; + } + } + if count == $e.len() { + Result::Complete(_i.clone(), $e) + } else { + Result::Fail(Error::new( + format!("Expected {} but didn't get it.", $e), + &$i, + )) + } + }}; +} + +/// Consumes an input until it reaches the term combinator matches. +/// +/// If the term never matches then returns incomplete. +/// ``` +/// # #[macro_use] extern crate abortable_parser; +/// use abortable_parser::iter; +/// # use abortable_parser::{Result, Offsetable}; +/// use std::convert::From; +/// # fn main() { +/// let iter: iter::SliceIter = "foo;".into(); +/// let tok = until!(iter, text_token!(";")); +/// # assert!(tok.is_complete()); +/// if let Result::Complete(i, o) = tok { +/// assert_eq!(i.get_offset(), 3); +/// } +/// # } +/// ``` +#[macro_export] +macro_rules! until { + ($i:expr, $term:ident!( $( $args:tt )* ) ) => {{ + use $crate::{Result, Offsetable}; + let mut acc = Vec::new(); + let mut _i = $i.clone(); + let pfn = || { + loop { + match $term!(_i.clone(), $($args)*) { + Result::Complete(_, _) => return Result::Complete(_i, acc), + Result::Abort(e) => return Result::Abort(e), + Result::Incomplete(offset) => return Result::Incomplete(offset), + Result::Fail(_) => { + // noop + } + } + let item = match _i.next() { + Some(it) => it, + None => return Result::Incomplete(_i.get_offset()), + }; + acc.push(item); + } + }; + pfn() + }}; + + ($i:expr, $term:ident) => { + consume_until!($i, run!($term)) + }; +} + +/// Maps a Result of type Vec<&u8> to a Result of type String. +pub fn must_string<'a, I, E>(matched: Result>, msg: E) -> Result +where + I: InputIter, + E: Into, +{ + match matched { + Result::Complete(i, mut o) => { + let new_string = String::from_utf8(o.drain(0..).map(|b| *b).collect()); + match new_string { + Ok(s) => Result::Complete(i, s), + Err(_) => Result::Abort(Error::new(msg, &i)), + } + }, + Result::Incomplete(offset) => Result::Incomplete(offset), + Result::Abort(e) => Result::Abort(e), + Result::Fail(e) => Result::Fail(e), + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 064760b..4ba1c23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -//! A parser combinator library with a focus on fully abortable parsing and error handling. -use std::fmt::{Debug, Display}; +//! An opinionated parser combinator library with a focus on fully abortable parsing and error handling. +use std::fmt::Display; use std::iter::Iterator; /// A trait for types that can have an offset as a count of processed items. @@ -20,38 +20,45 @@ pub trait InputIter: Iterator + Clone + Offsetable {} /// Stores a wrapped err that must implement Display as well as an offset and /// an optional cause. #[derive(Debug)] -pub struct Error { - msg: E, +pub struct Error { + msg: String, offset: usize, - cause: Option>>, + cause: Option>, } -impl Error { +impl Error { /// Constructs a new Error with an offset and no cause. - pub fn new(err: E, offset: &S) -> Self { + pub fn new(msg: M, offset: &S) -> Self + where + S: Offsetable, + M: Into { Error { - msg: err, + msg: msg.into(), offset: offset.get_offset(), cause: None, } } /// Constructs a new Error with an offset and a cause. - pub fn caused_by(msg: E, offset: &S, cause: Self) -> Self { + pub fn caused_by(msg: M, offset: &S, cause: Self) -> Self + where + S: Offsetable, + M: Into { + Error { - msg: msg, + msg: msg.into(), offset: offset.get_offset(), cause: Some(Box::new(cause)), } } /// Returns the contained err. - pub fn get_msg<'a>(&'a self) -> &'a E { + pub fn get_msg<'a>(&'a self) -> &'a str { &self.msg } /// Returns `Some(cause)` if there is one, None otherwise. - pub fn get_cause<'a>(&'a self) -> Option<&'a Error> { + pub fn get_cause<'a>(&'a self) -> Option<&'a Error> { match self.cause { Some(ref cause) => Some(cause), None => None, @@ -64,7 +71,7 @@ impl Error { } } -impl Display for Error { +impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { try!(write!(f, "{}", self.msg)); match self.cause { @@ -76,19 +83,19 @@ impl Display for Error { /// 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(Error), + Fail(Error), /// Abort represents a match failure that the parser cannot recover from. - Abort(Error), + Abort(Error), } -impl Result { +impl Result { /// Returns true if the Result is Complete. pub fn is_complete(&self) -> bool { if let &Result::Complete(_, _) = self { @@ -126,8 +133,6 @@ pub use iter::SliceIter; #[macro_use] pub mod combinators; -#[macro_use] -pub mod matchers; pub mod iter; #[cfg(test)] diff --git a/src/matchers.rs b/src/matchers.rs deleted file mode 100644 index 443e436..0000000 --- a/src/matchers.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Contains matchers for matching specific patterns or tokens. -use super::{InputIter, Result, Error}; - -use std::fmt::{Debug, Display}; - -/// Convenience macro for looking for a specific text token in a byte input stream. -/// -/// ``` -/// # #[macro_use] extern crate abortable_parser; -/// use abortable_parser::iter; -/// # use abortable_parser::{Result, Offsetable}; -/// use std::convert::From; -/// # fn main() { -/// let iter: iter::SliceIter = "foo bar".into(); -/// let tok = text_token!(iter, "foo"); -/// # assert!(tok.is_complete()); -/// if let Result::Complete(i, o) = tok { -/// assert_eq!(i.get_offset(), 3); -/// assert_eq!(o, "foo"); -/// } -/// # } -/// ``` -#[macro_export] -macro_rules! text_token { - ($i:expr, $e:expr) => {{ - use $crate::Error; - use $crate::Result; - let mut _i = $i.clone(); - let mut count = 0; - for expected in $e.bytes() { - let item = match _i.next() { - Some(item) => item, - None => break, - }; - if item == &expected { - count += 1; - } - } - if count == $e.len() { - Result::Complete(_i.clone(), $e) - } else { - Result::Fail(Error::new( - format!("Expected {} but didn't get it.", $e), - &$i, - )) - } - }}; -} - -/// Consumes an input until it reaches the term combinator matches. -/// -/// If the term never matches then returns incomplete. -/// ``` -/// # #[macro_use] extern crate abortable_parser; -/// use abortable_parser::iter; -/// # use abortable_parser::{Result, Offsetable}; -/// use std::convert::From; -/// # fn main() { -/// let iter: iter::SliceIter = "foo;".into(); -/// let tok = until!(iter, text_token!(";")); -/// # assert!(tok.is_complete()); -/// if let Result::Complete(i, o) = tok { -/// assert_eq!(i.get_offset(), 3); -/// } -/// # } -/// ``` -#[macro_export] -macro_rules! until { - ($i:expr, $term:ident!( $( $args:tt )* ) ) => {{ - use $crate::{Result, Offsetable}; - let mut acc = Vec::new(); - let mut _i = $i.clone(); - let pfn = || { - loop { - match $term!(_i.clone(), $($args)*) { - Result::Complete(_, _) => return Result::Complete(_i, acc), - Result::Abort(e) => return Result::Abort(e), - Result::Incomplete(offset) => return Result::Incomplete(offset), - Result::Fail(_) => { - // noop - } - } - let item = match _i.next() { - Some(it) => it, - None => return Result::Incomplete(_i.get_offset()), - }; - acc.push(item); - } - }; - pfn() - }}; - - ($i:expr, $term:ident) => { - consume_until!($i, run!($term)) - }; -} - -/// Maps a Result of type Vec<&u8> to a Result of type String. -pub fn must_string<'a, I, E>(matched: Result, E>, msg: E) -> Result -where - I: InputIter, - E: Debug + Display, -{ - match matched { - Result::Complete(i, mut o) => { - let new_string = String::from_utf8(o.drain(0..).map(|b| *b).collect()); - match new_string { - Ok(s) => Result::Complete(i, s), - Err(_) => Result::Abort(Error::new(msg, &i)), - } - }, - Result::Incomplete(offset) => Result::Incomplete(offset), - Result::Abort(e) => Result::Abort(e), - Result::Fail(e) => Result::Fail(e), - } -} \ No newline at end of file diff --git a/src/test.rs b/src/test.rs index 73320e1..7203fd2 100644 --- a/src/test.rs +++ b/src/test.rs @@ -2,7 +2,7 @@ use std::fmt::{Debug, Display}; use super::{InputIter, Offsetable, Result}; use iter::SliceIter; -use matchers::must_string; +use combinators::must_string; #[test] fn test_slice_iter() { @@ -34,7 +34,7 @@ fn test_slice_iter() { assert_eq!('o' as u8, out[2]); } -fn will_fail(i: I) -> Result +fn will_fail(i: I) -> Result where I: InputIter, C: Debug + Display, @@ -42,7 +42,7 @@ where Result::Fail(super::Error::new("AAAAHHH!!!".to_string(), &i)) } -fn parse_byte<'a, I>(mut i: I) -> Result +fn parse_byte<'a, I>(mut i: I) -> Result where I: InputIter, { @@ -52,14 +52,14 @@ where } } -fn will_not_complete<'a, I>(_: I) -> Result +fn will_not_complete<'a, I>(_: I) -> Result where I: InputIter, { Result::Incomplete(0) } -fn parse_three<'a, I>(i: I) -> Result +fn parse_three<'a, I>(i: I) -> Result where I: InputIter, {