FEATURE: More helper macros and parser combinators.

This commit is contained in:
Jeremy Wall 2018-09-12 19:33:11 -05:00
parent beca789911
commit a5ca5a92ce
4 changed files with 251 additions and 0 deletions

View File

@ -676,3 +676,77 @@ macro_rules! until {
consume_until!($i, run!($term))
};
}
/// Discards the output of a combinator rule when it completes and just returns `()`.
/// Leaves Failures, Aborts, and Incompletes untouched.
#[macro_export]
macro_rules! discard {
($i:expr, $term:ident) => {
discard!($i, run!($term))
};
($i:expr, $term:ident!( $( $args:tt )* ) ) => {{
use $crate::Result;
match $term!($i, $($args)*) {
Result::Complete(i, _) => Result::Complete(i, ()),
Result::Incomplete(offset) => Result::Incomplete(offset),
Result::Fail(e) => Result::Fail(e),
Result::Abort(e) => Result::Abort(e),
}
}};
}
/// Matches and returns any ascii charactar whitespace byte.
pub fn ascii_ws<'a, I: InputIter<Item=&'a u8>>(mut i: I) -> Result<I, u8> {
match i.next() {
Some(b) => {
match b {
b'\r' => Result::Complete(i, *b),
b'\n' => Result::Complete(i, *b),
b'\t' => Result::Complete(i, *b),
b' ' => Result::Complete(i, *b),
_ => Result::Fail(Error::new("Not whitespace", &i)),
}
},
None => {
Result::Fail(Error::new("Unexpected End Of Input", &i))
}
}
}
/// Matches the end of input for any InputIter.
/// Returns `()` for any match.
pub fn eoi<I: InputIter>(i: I) -> Result<I, ()> {
let mut _i = i.clone();
match _i.next() {
Some(_) => Result::Fail(Error::new("Expected End Of Input", &i)),
None => Result::Complete(i, ()),
}
}
/// constructs a function named $name that takes an input of type $i and produces an output
/// of type $o.
///
#[macro_export]
macro_rules! make_fn {
($name:ident<$i:ty, $o:ty>, $rule:ident!($( $body:tt )* )) => {
fn $name(i: $i) -> Result<$i,$o> {
$rule!(i, $($body)*)
}
};
(pub $name:ident<$i:ty, $o:ty>, $rule:ident!($( $body:tt )* )) => {
pub fn $name(i: $i) -> Result<$i,$o> {
$rule!(i, $($body)*)
}
};
($name:ident<$i:ty, $o:ty>, $rule:ident) => {
make_fn!($name<$i, $o>, run!($rule))
};
(pub $name:ident<$i:ty, $o:ty>, $rule:ident) => {
make_fn!(pub $name<$i, $o>, run!($rule))
};
}

42
src/integration_tests.rs Normal file
View File

@ -0,0 +1,42 @@
use iter::StrIter;
use super::{Result, eoi, ascii_ws};
make_fn!(proto<StrIter, &str>,
do_each!(
proto => until!(text_token!("://")),
_ => must!(text_token!("://")),
(proto)
)
);
make_fn!(domain<StrIter, &str>,
until!(either!(
discard!(text_token!("/")),
discard!(ascii_ws),
eoi))
);
make_fn!(path<StrIter, &str>,
until!(either!(discard!(ascii_ws), eoi))
);
make_fn!(pub url<StrIter, (Option<&str>, Option<&str>, &str)>,
do_each!(
protocol => optional!(proto),
domain => optional!(domain),
path => path,
(protocol, domain, path)
)
);
#[test]
fn test_url_parser() {
let iter = StrIter::new("http://example.com/some/path ");
let result = url(iter);
assert!(result.is_complete());
if let Result::Complete(_, (proto, domain, path)) = result {
assert!(proto.is_some());
assert!(domain.is_some());
assert_eq!(path, "/some/path");
}
}

View File

@ -1,4 +1,52 @@
//! An opinionated parser combinator library with a focus on fully abortable parsing and error handling.
//!
//! # Example
//!
//! ```
//! #[macro_use]
//! extern crate abortable_parser;
//! use abortable_parser::iter::StrIter;
//! use abortable_parser::{Result, eoi, ascii_ws};
//!
//! make_fn!(proto<StrIter, &str>,
//! do_each!(
//! proto => until!(text_token!("://")),
//! _ => must!(text_token!("://")),
//! (proto)
//! )
//! );
//!
//! make_fn!(domain<StrIter, &str>,
//! until!(either!(
//! discard!(text_token!("/")),
//! discard!(ascii_ws),
//! eoi))
//! );
//!
//! make_fn!(path<StrIter, &str>,
//! until!(either!(discard!(ascii_ws), eoi))
//! );
//!
//! make_fn!(url<StrIter, (Option<&str>, Option<&str>, &str)>,
//! do_each!(
//! protocol => optional!(proto),
//! domain => optional!(domain),
//! path => path,
//! (protocol, domain, path)
//! )
//! );
//!
//! # fn main() {
//! let iter = StrIter::new("http://example.com/some/path ");
//! let result = url(iter);
//! assert!(result.is_complete());
//! if let Result::Complete(_, (proto, domain, path)) = result {
//! assert!(proto.is_some());
//! assert!(domain.is_some());
//! assert_eq!(path, "/some/path");
//! }
//! # }
//! ```
use std::fmt::Display;
use std::iter::Iterator;
@ -142,6 +190,7 @@ impl<I: InputIter, O> Result<I, O> {
}
pub use iter::SliceIter;
pub use combinators::*;
#[macro_use]
pub mod combinators;
@ -149,3 +198,5 @@ pub mod iter;
#[cfg(test)]
mod test;
#[cfg(test)]
mod integration_tests;

View File

@ -2,6 +2,7 @@ use std::fmt::{Debug, Display};
use super::{InputIter, Offsetable, Result};
use iter::{StrIter, SliceIter};
use combinators::*;
#[test]
fn test_slice_iter() {
@ -378,3 +379,86 @@ fn test_until_incomplete() {
let result = until!(iter, text_token!("; "));
assert!(result.is_incomplete());
}
#[test]
fn test_discard_success() {
let input_str = "foo";
let iter = SliceIter::new(input_str.as_bytes());
let result = discard!(iter, text_token!("foo"));
assert!(result.is_complete());
if let Result::Complete(_, o) = result {
assert_eq!(o, ());
}
}
#[test]
fn test_discard_fail() {
let input_str = "foo";
let iter = SliceIter::new(input_str.as_bytes());
let result = discard!(iter, will_fail);
assert!(result.is_fail());
}
#[test]
fn test_discard_abort() {
let input_str = "foo";
let iter = SliceIter::new(input_str.as_bytes());
let result = discard!(iter, must!(will_fail));
assert!(result.is_abort());
}
#[test]
fn test_discard_incomplete() {
let input_str = "foo";
let iter = SliceIter::new(input_str.as_bytes());
let result = discard!(iter, will_not_complete);
assert!(result.is_incomplete());
}
#[test]
fn test_eoi_success() {
let input_str = "";
let iter = StrIter::new(input_str);
let result = eoi(iter);
assert!(result.is_complete());
}
#[test]
fn test_eoi_fail() {
let input_str = " ";
let iter = StrIter::new(input_str);
let result = eoi(iter);
assert!(result.is_fail());
}
#[test]
fn test_ascii_ws_space() {
let input_str = " ";
let iter = StrIter::new(input_str);
let result = ascii_ws(iter);
assert!(result.is_complete());
}
#[test]
fn test_ascii_ws_tab() {
let input_str = "\t";
let iter = StrIter::new(input_str);
let result = ascii_ws(iter);
assert!(result.is_complete());
}
#[test]
fn test_ascii_ws_newline() {
let input_str = "\n";
let iter = StrIter::new(input_str);
let result = ascii_ws(iter);
assert!(result.is_complete());
}
#[test]
fn test_ascii_ws_carriage_return() {
let input_str = "\r";
let iter = StrIter::new(input_str);
let result = ascii_ws(iter);
assert!(result.is_complete());
}