mirror of
https://github.com/zaphar/abortable_parser.git
synced 2025-07-21 20:29:49 -04:00
FEATURE: A mass of bugfixes and ergonomic improvements.
This commit is contained in:
parent
79ee6841e4
commit
d9de070873
@ -13,7 +13,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
//! Contains combinators that can assemble other matchers or combinators into more complex grammars.
|
//! Contains combinators that can assemble other matchers or combinators into more complex grammars.
|
||||||
|
|
||||||
use super::{Error, InputIter, Result};
|
use super::{Error, InputIter, Result};
|
||||||
|
|
||||||
/// Turns a `Result` to it's inverse.
|
/// Turns a `Result` to it's inverse.
|
||||||
@ -176,8 +175,8 @@ macro_rules! wrap_err {
|
|||||||
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::Incomplete(offset),
|
$crate::Result::Incomplete(offset) => $crate::Result::Incomplete(offset),
|
||||||
$crate::Result::Fail(e) => $crate::Result::Fail($crate::Error::caused_by($e, &_i, e)),
|
$crate::Result::Fail(e) => $crate::Result::Fail($crate::Error::caused_by($e, &_i, Box::new(e))),
|
||||||
$crate::Result::Abort(e) => $crate::Result::Abort($crate::Error::caused_by($e, &_i, e)),
|
$crate::Result::Abort(e) => $crate::Result::Abort($crate::Error::caused_by($e, &_i, Box::new(e))),
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@ -237,10 +236,9 @@ macro_rules! trap {
|
|||||||
/// You must specify the error message to use in case the matcher is incomplete.
|
/// 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.
|
/// The must_complete! macro provides syntactic sugar for using this combinator.
|
||||||
pub fn must_complete<I, O, M>(result: Result<I, O>, msg: M) -> Result<I, O>
|
pub fn must_complete<I, O>(result: Result<I, O>, msg: String) -> Result<I, O>
|
||||||
where
|
where
|
||||||
I: InputIter,
|
I: InputIter,
|
||||||
M: Into<String>,
|
|
||||||
{
|
{
|
||||||
match result {
|
match result {
|
||||||
Result::Complete(i, o) => Result::Complete(i, o),
|
Result::Complete(i, o) => Result::Complete(i, o),
|
||||||
@ -250,6 +248,32 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns `Result::Incomplete` into `Result::Fail`.
|
||||||
|
pub fn complete<I, O, S>(result: Result<I, O>, msg: S) -> Result<I, O>
|
||||||
|
where
|
||||||
|
I: InputIter,
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
match result {
|
||||||
|
Result::Incomplete(offset) => Result::Fail(Error::new(msg.into(), &offset)),
|
||||||
|
Result::Complete(i, o) => Result::Complete(i, o),
|
||||||
|
Result::Fail(e) => Result::Fail(e),
|
||||||
|
Result::Abort(e) => Result::Abort(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns `Result::Incomplete` into `Result::Fail`.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! complete {
|
||||||
|
($i:expr, $e:expr, $f:ident!( $( $args:tt )* ) ) => {
|
||||||
|
$crate::combinators::complete($f!($i, $($args)*), $e)
|
||||||
|
};
|
||||||
|
|
||||||
|
($i:expr, $efn:expr, $f:ident) => {
|
||||||
|
complete!($i, $efn, run!($f))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Turns `Result::Fail` and `Result::Incomplete` into `Result::Abort`.
|
/// Turns `Result::Fail` and `Result::Incomplete` into `Result::Abort`.
|
||||||
///
|
///
|
||||||
/// You must specify the error message to use in case the matcher is incomplete.
|
/// You must specify the error message to use in case the matcher is incomplete.
|
||||||
@ -594,6 +618,53 @@ macro_rules! repeat {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! separated {
|
||||||
|
($i:expr, $sep_rule:ident!( $( $sep_args:tt )* ), $item_rule:ident!( $( $item_args:tt )* ) ) => {{
|
||||||
|
use $crate::Result;
|
||||||
|
let _i = $i.clone();
|
||||||
|
// We require at least one item for our list
|
||||||
|
let head = $item_rule!($i.clone(), $($item_args)*);
|
||||||
|
match head {
|
||||||
|
Result::Incomplete(offset) => Result::Incomplete(offset),
|
||||||
|
Result::Fail(e) => Result::Fail(e),
|
||||||
|
Result::Abort(e) => Result::Abort(e),
|
||||||
|
Result::Complete(i,item) => {
|
||||||
|
let mut list = vec![item];
|
||||||
|
// Now we parse a repeat of sep_rule and item_rule.
|
||||||
|
let tail_result = repeat!(i,
|
||||||
|
do_each!(
|
||||||
|
_ => $sep_rule!($($sep_args)*),
|
||||||
|
item => $item_rule!($($item_args)*),
|
||||||
|
(item)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
match tail_result {
|
||||||
|
Result::Fail(e) => Result::Fail(e),
|
||||||
|
Result::Incomplete(offset) => Result::Incomplete(offset),
|
||||||
|
Result::Abort(e) => Result::Abort(e),
|
||||||
|
Result::Complete(i, mut tail) => {
|
||||||
|
list.extend(tail.drain(0..));
|
||||||
|
Result::Complete(i, list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
($i:expr, $sep_rule:ident, $item_rule:ident ) => {
|
||||||
|
separated!($i, run!($sep_rule), run!($item_rule))
|
||||||
|
};
|
||||||
|
|
||||||
|
($i:expr, $sep_rule:ident!( $( $args:tt )* ), $item_rule:ident ) => {
|
||||||
|
separated!($i, $sep_rule!($($args)*), run!($item_rule))
|
||||||
|
};
|
||||||
|
|
||||||
|
($i:expr, $sep_rule:ident, $item_rule:ident!( $( $args:tt )* ) ) => {
|
||||||
|
separated!($i, run!($sep_rule), $item_rule!($($args)*))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Convenience macro for looking for a specific text token in a byte input stream.
|
/// Convenience macro for looking for a specific text token in a byte input stream.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -683,7 +754,7 @@ macro_rules! until {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
($i:expr, $rule:ident) => {
|
($i:expr, $rule:ident) => {
|
||||||
consume_until!($i, run!($rule))
|
until!($i, run!($rule))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -709,14 +780,14 @@ macro_rules! discard {
|
|||||||
/// Matches and returns any ascii charactar whitespace byte.
|
/// Matches and returns any ascii charactar whitespace byte.
|
||||||
pub fn ascii_ws<'a, I: InputIter<Item = &'a u8>>(mut i: I) -> Result<I, u8> {
|
pub fn ascii_ws<'a, I: InputIter<Item = &'a u8>>(mut i: I) -> Result<I, u8> {
|
||||||
match i.next() {
|
match i.next() {
|
||||||
Some(b) => match b {
|
Some(b) => {
|
||||||
b'\r' => Result::Complete(i, *b),
|
if (*b as char).is_whitespace() {
|
||||||
b'\n' => Result::Complete(i, *b),
|
Result::Complete(i, *b)
|
||||||
b'\t' => Result::Complete(i, *b),
|
} else {
|
||||||
b' ' => Result::Complete(i, *b),
|
Result::Fail(Error::new("Not whitespace".to_string(), &i))
|
||||||
_ => Result::Fail(Error::new("Not whitespace", &i)),
|
}
|
||||||
},
|
},
|
||||||
None => Result::Fail(Error::new("Unexpected End Of Input", &i)),
|
None => Result::Fail(Error::new("Unexpected End Of Input".to_string(), &i)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -725,7 +796,7 @@ pub fn ascii_ws<'a, I: InputIter<Item = &'a u8>>(mut i: I) -> Result<I, u8> {
|
|||||||
pub fn eoi<I: InputIter>(i: I) -> Result<I, ()> {
|
pub fn eoi<I: InputIter>(i: I) -> Result<I, ()> {
|
||||||
let mut _i = i.clone();
|
let mut _i = i.clone();
|
||||||
match _i.next() {
|
match _i.next() {
|
||||||
Some(_) => Result::Fail(Error::new("Expected End Of Input", &i)),
|
Some(_) => Result::Fail(Error::new("Expected End Of Input".to_string(), &i)),
|
||||||
None => Result::Complete(i, ()),
|
None => Result::Complete(i, ()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -753,34 +824,138 @@ pub fn eoi<I: InputIter>(i: I) -> Result<I, ()> {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! make_fn {
|
macro_rules! make_fn {
|
||||||
($name:ident<$i:ty, $o:ty>, $rule:ident!($( $body:tt )* )) => {
|
($name:ident<$i:ty, $o:ty>, $rule:ident!($( $body:tt )* )) => {
|
||||||
fn $name(i: $i) -> $crate::Result<$i,$o> {
|
fn $name(i: $i) -> $crate::Result<$i, $o> {
|
||||||
$rule!(i, $($body)*)
|
$rule!(i, $($body)*)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(pub $name:ident<$i:ty, $o:ty>, $rule:ident!($( $body:tt )* )) => {
|
(pub $name:ident<$i:ty, $o:ty>, $rule:ident!($( $body:tt )* )) => {
|
||||||
pub fn $name(i: $i) -> $crate::Result<$i,$o> {
|
pub fn $name(i: $i) -> $crate::Result<$i, $o> {
|
||||||
$rule!(i, $($body)*)
|
$rule!(i, $($body)*)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($name:ident<$i:ty, $o:ty>, $rule:ident) => {
|
($name:ident<$i:ty, $o:ty>, $rule:ident) => {
|
||||||
make_fn!($name<$i, $o>, run!($rule))
|
make_fn!($name<$i, $o>, run!($rule));
|
||||||
};
|
};
|
||||||
|
|
||||||
(pub $name:ident<$i:ty, $o:ty>, $rule:ident) => {
|
(pub $name:ident<$i:ty, $o:ty>, $rule:ident) => {
|
||||||
make_fn!(pub $name<$i, $o>, run!($rule))
|
make_fn!(pub $name<$i, $o>, run!($rule));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper macro that returns the input without consuming it.
|
||||||
|
///
|
||||||
|
/// Useful when you need to get the input and use it to retrieve
|
||||||
|
/// positional information like offset or line and column.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! input {
|
||||||
|
($i:expr) => {
|
||||||
|
input!($i,)
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
($i:expr,) => {{
|
||||||
|
|
||||||
/// For inputs that implement the TextPositionTracker trait returns the current
|
|
||||||
/// line and column position for this input.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! pos {
|
|
||||||
($i:expr) => {{
|
|
||||||
let _i = $i.clone();
|
let _i = $i.clone();
|
||||||
use $crate::TextPositionTracker;
|
$crate::Result::Complete($i, _i)
|
||||||
$crate::Result::Complete($i, (_i.line(), _i.column()))
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the input until the $rule fails and then returns the consumed input as
|
||||||
|
/// a slice.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[macro_use] extern crate abortable_parser;
|
||||||
|
/// use abortable_parser::iter;
|
||||||
|
/// # use abortable_parser::{Result, Offsetable};
|
||||||
|
/// # use abortable_parser::combinators::ascii_alpha;
|
||||||
|
/// use std::convert::From;
|
||||||
|
/// # fn main() {
|
||||||
|
/// let iter: iter::StrIter = "foo;".into();
|
||||||
|
/// let tok = consume_all!(iter, ascii_alpha);
|
||||||
|
/// # 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! consume_all {
|
||||||
|
($i:expr, $rule:ident!( $( $args:tt )* ) ) => {{
|
||||||
|
use $crate::{Result, Offsetable, Span, SpanRange};
|
||||||
|
let start_offset = $i.get_offset();
|
||||||
|
let mut _i = $i.clone();
|
||||||
|
let pfn = || {
|
||||||
|
loop {
|
||||||
|
match $rule!(_i.clone(), $($args)*) {
|
||||||
|
Result::Complete(_, _) => {
|
||||||
|
// noop
|
||||||
|
},
|
||||||
|
Result::Abort(e) => return Result::Abort(e),
|
||||||
|
Result::Incomplete(offset) => return Result::Incomplete(offset),
|
||||||
|
Result::Fail(_) => {
|
||||||
|
let range = SpanRange::Range(start_offset.._i.get_offset());
|
||||||
|
return Result::Complete(_i, $i.span(range));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let None = _i.next() {
|
||||||
|
return Result::Incomplete(_i.get_offset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pfn()
|
||||||
|
}};
|
||||||
|
|
||||||
|
($i:expr, $rule:ident) => {
|
||||||
|
consume_all!($i, run!($rule))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ascii_digit parses a single ascii alphabetic or digit character from an InputIter of bytes.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ascii_alphanumeric<'a, I: InputIter<Item=&'a u8>>(mut i: I) -> Result<I, u8> {
|
||||||
|
match i.next() {
|
||||||
|
Some(b) => {
|
||||||
|
let c = *b as char;
|
||||||
|
if c.is_ascii_alphabetic() || c.is_ascii_digit() {
|
||||||
|
Result::Complete(i, *b)
|
||||||
|
} else {
|
||||||
|
Result::Fail(Error::new("Not an alphanumeric character".to_string(), &i))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Result::Fail(Error::new("Unexpected End Of Input.".to_string(), &i)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ascii_digit parses a single ascii digit character from an InputIter of bytes.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ascii_digit<'a, I: InputIter<Item = &'a u8>>(mut i: I) -> Result<I, u8> {
|
||||||
|
match i.next() {
|
||||||
|
Some(b) => {
|
||||||
|
if (*b as char).is_ascii_digit() {
|
||||||
|
Result::Complete(i, *b)
|
||||||
|
} else {
|
||||||
|
Result::Fail(Error::new("Not an digit character".to_string(), &i))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Result::Fail(Error::new("Unexpected End Of Input.".to_string(), &i)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ascii_alpha parses a single ascii alphabet character from an InputIter of bytes.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ascii_alpha<'a, I: InputIter<Item = &'a u8>>(mut i: I) -> Result<I, u8> {
|
||||||
|
match i.next() {
|
||||||
|
Some(b) => {
|
||||||
|
if (*b as char).is_ascii_alphabetic() {
|
||||||
|
Result::Complete(i, *b)
|
||||||
|
} else {
|
||||||
|
Result::Fail(Error::new("Not an alpha character".to_string(), &i))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Result::Fail(Error::new("Unexpected End Of Input.".to_string(), &i)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jwall): We need a helper to convert Optional into failures.
|
||||||
|
// TODO(jwall): We need a helper to convert std::result::Result into failures.
|
@ -11,9 +11,8 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::{ascii_ws, eoi, Result};
|
use super::{ascii_ws, eoi, Result};
|
||||||
use iter::StrIter;
|
use iter::{StrIter, SliceIter};
|
||||||
|
|
||||||
make_fn!(proto<StrIter, &str>,
|
make_fn!(proto<StrIter, &str>,
|
||||||
do_each!(
|
do_each!(
|
||||||
@ -34,8 +33,25 @@ make_fn!(path<StrIter, &str>,
|
|||||||
until!(either!(discard!(ascii_ws), eoi))
|
until!(either!(discard!(ascii_ws), eoi))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
make_fn!(sliceit<SliceIter<u8>, ()>,
|
||||||
|
do_each!(
|
||||||
|
_ => input!(),
|
||||||
|
end_of_input => eoi,
|
||||||
|
(end_of_input)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
make_fn!(long_string_path<SliceIter<u8>, ()>,
|
||||||
|
do_each!(
|
||||||
|
_ => input!(),
|
||||||
|
end_of_input => eoi,
|
||||||
|
(end_of_input)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
make_fn!(pub url<StrIter, (Option<&str>, Option<&str>, &str)>,
|
make_fn!(pub url<StrIter, (Option<&str>, Option<&str>, &str)>,
|
||||||
do_each!(
|
do_each!(
|
||||||
|
_ => input!(),
|
||||||
protocol => optional!(proto),
|
protocol => optional!(proto),
|
||||||
domain => optional!(domain),
|
domain => optional!(domain),
|
||||||
path => path,
|
path => path,
|
||||||
@ -54,3 +70,17 @@ fn test_url_parser() {
|
|||||||
assert_eq!(path, "/some/path");
|
assert_eq!(path, "/some/path");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_iter_make_fn() {
|
||||||
|
let iter = SliceIter::from("yo!");
|
||||||
|
let result = sliceit(iter);
|
||||||
|
assert!(result.is_fail());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_iter_make_fn_long_error_path() {
|
||||||
|
let iter = SliceIter::from("yo!");
|
||||||
|
let result = long_string_path(iter);
|
||||||
|
assert!(result.is_fail());
|
||||||
|
}
|
||||||
|
29
src/iter.rs
29
src/iter.rs
@ -16,7 +16,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
|
|
||||||
use super::{InputIter, Offsetable, Span, SpanRange, TextPositionTracker};
|
use super::{InputIter, Offsetable, Seekable, Span, SpanRange, TextPositionTracker};
|
||||||
|
|
||||||
/// Implements `InputIter` for any slice of T.
|
/// Implements `InputIter` for any slice of T.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -95,6 +95,12 @@ impl<'a, T: Debug> From<&'a Vec<T>> for SliceIter<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, O: Debug> Peekable<&'a O> for SliceIter<'a, O> {
|
||||||
|
fn peek_next(&self) -> Option<&'a O> {
|
||||||
|
self.source.get(self.offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Implements `InputIter` for any slice of T.
|
/// Implements `InputIter` for any slice of T.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StrIter<'a> {
|
pub struct StrIter<'a> {
|
||||||
@ -184,3 +190,24 @@ impl<'a> Span<&'a str> for StrIter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Seekable for StrIter<'a> {
|
||||||
|
fn seek(&mut self, to: usize) -> usize {
|
||||||
|
let self_len = self.source.len();
|
||||||
|
let offset = if self_len > to {
|
||||||
|
to
|
||||||
|
} else {
|
||||||
|
self_len
|
||||||
|
};
|
||||||
|
self.offset = offset;
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use super::Peekable;
|
||||||
|
|
||||||
|
impl<'a> Peekable<&'a u8> for StrIter<'a> {
|
||||||
|
fn peek_next(&self) -> Option<&'a u8> {
|
||||||
|
self.source.as_bytes().get(self.offset)
|
||||||
|
}
|
||||||
|
}
|
41
src/lib.rs
41
src/lib.rs
@ -104,6 +104,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
|
use std::result;
|
||||||
|
|
||||||
/// A trait for types that can have an offset as a count of processed items.
|
/// A trait for types that can have an offset as a count of processed items.
|
||||||
pub trait Offsetable {
|
pub trait Offsetable {
|
||||||
@ -116,6 +117,10 @@ impl Offsetable for usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Seekable {
|
||||||
|
fn seek(&mut self, usize) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait for Inputs that can track lines and columns in a text input.
|
/// Trait for Inputs that can track lines and columns in a text input.
|
||||||
pub trait TextPositionTracker {
|
pub trait TextPositionTracker {
|
||||||
fn line(&self) -> usize;
|
fn line(&self) -> usize;
|
||||||
@ -135,6 +140,10 @@ pub trait Span<O> {
|
|||||||
fn span(&self, idx: SpanRange) -> O;
|
fn span(&self, idx: SpanRange) -> O;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Peekable<O> {
|
||||||
|
fn peek_next(&self) -> Option<O>;
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 + Offsetable {}
|
pub trait InputIter: Iterator + Clone + Offsetable {}
|
||||||
|
|
||||||
@ -142,7 +151,8 @@ pub trait InputIter: Iterator + Clone + Offsetable {}
|
|||||||
/// Stores a wrapped err that must implement Display as well as an offset and
|
/// Stores a wrapped err that must implement Display as well as an offset and
|
||||||
/// an optional cause.
|
/// an optional cause.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error {
|
pub struct Error
|
||||||
|
{
|
||||||
msg: String,
|
msg: String,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
cause: Option<Box<Error>>,
|
cause: Option<Box<Error>>,
|
||||||
@ -150,10 +160,9 @@ pub struct Error {
|
|||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
/// Constructs a new Error with an offset and no cause.
|
/// Constructs a new Error with an offset and no cause.
|
||||||
pub fn new<S, M>(msg: M, offset: &S) -> Self
|
pub fn new<S, D: Into<String>>(msg: D, offset: &S) -> Self
|
||||||
where
|
where
|
||||||
S: Offsetable,
|
S: Offsetable,
|
||||||
M: Into<String>,
|
|
||||||
{
|
{
|
||||||
Error {
|
Error {
|
||||||
msg: msg.into(),
|
msg: msg.into(),
|
||||||
@ -163,28 +172,27 @@ impl Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new Error with an offset and a cause.
|
/// Constructs a new Error with an offset and a cause.
|
||||||
pub fn caused_by<S, M>(msg: M, offset: &S, cause: Self) -> Self
|
pub fn caused_by<'a, S, D: Into<String>>(msg: D, offset: &'a S, cause: Box<Self>) -> Self
|
||||||
where
|
where
|
||||||
S: Offsetable,
|
S: Offsetable,
|
||||||
M: Into<String>,
|
|
||||||
{
|
{
|
||||||
Error {
|
Error {
|
||||||
msg: msg.into(),
|
msg: msg.into(),
|
||||||
offset: offset.get_offset(),
|
offset: offset.get_offset(),
|
||||||
cause: Some(Box::new(cause)),
|
cause: Some(cause),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the contained err.
|
/// Returns the msg.
|
||||||
pub fn get_msg<'a>(&'a self) -> &'a str {
|
pub fn get_msg<'a>(&'a self) -> String {
|
||||||
&self.msg
|
format!("{}", &self.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some(cause)` if there is one, None otherwise.
|
/// 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 {
|
match self.cause {
|
||||||
Some(ref cause) => Some(cause),
|
Some(ref e) => Some(e),
|
||||||
None => None,
|
None => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +203,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> {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> result::Result<(), std::fmt::Error> {
|
||||||
try!(write!(f, "{}", self.msg));
|
try!(write!(f, "{}", self.msg));
|
||||||
match self.cause {
|
match self.cause {
|
||||||
Some(ref c) => write!(f, "\n\tCaused By:{}", c),
|
Some(ref c) => write!(f, "\n\tCaused By:{}", c),
|
||||||
@ -204,9 +212,12 @@ impl Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
/// The result of a parsing attempt.
|
/// The result of a parsing attempt.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Result<I: InputIter, O> {
|
pub enum Result<I: InputIter, O>
|
||||||
|
{
|
||||||
/// 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.
|
||||||
@ -218,7 +229,8 @@ pub enum Result<I: InputIter, O> {
|
|||||||
Abort(Error),
|
Abort(Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: InputIter, O> Result<I, O> {
|
impl<I: InputIter, O> Result<I, O>
|
||||||
|
{
|
||||||
/// 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 {
|
||||||
@ -254,6 +266,7 @@ impl<I: InputIter, O> Result<I, O> {
|
|||||||
|
|
||||||
pub use combinators::*;
|
pub use combinators::*;
|
||||||
pub use iter::SliceIter;
|
pub use iter::SliceIter;
|
||||||
|
pub use iter::StrIter;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod combinators;
|
pub mod combinators;
|
||||||
|
164
src/test.rs
164
src/test.rs
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
use super::{InputIter, Offsetable, Result};
|
use super::{InputIter, Offsetable, Result, TextPositionTracker};
|
||||||
use combinators::*;
|
use combinators::*;
|
||||||
use iter::{SliceIter, StrIter};
|
use iter::{SliceIter, StrIter};
|
||||||
|
|
||||||
@ -252,6 +252,23 @@ fn test_do_each() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_do_each_input_and_token() {
|
||||||
|
let input_str = "foo";
|
||||||
|
let iter = SliceIter::new(input_str.as_bytes());
|
||||||
|
let result = do_each!(iter,
|
||||||
|
_ => input!(),
|
||||||
|
token => text_token!("foo"),
|
||||||
|
(token)
|
||||||
|
);
|
||||||
|
assert!(result.is_complete());
|
||||||
|
if let Result::Complete(_, o) = result {
|
||||||
|
assert_eq!("foo", o);
|
||||||
|
} else {
|
||||||
|
assert!(false, "did not get our token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_either_idents() {
|
fn test_either_idents() {
|
||||||
let input_str = "foo";
|
let input_str = "foo";
|
||||||
@ -477,7 +494,6 @@ fn test_ascii_ws_carriage_return() {
|
|||||||
assert!(result.is_complete());
|
assert!(result.is_complete());
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::TextPositionTracker;
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_position_tracking_striter() {
|
fn test_position_tracking_striter() {
|
||||||
let input_str = "\n";
|
let input_str = "\n";
|
||||||
@ -487,10 +503,146 @@ fn test_position_tracking_striter() {
|
|||||||
iter.next();
|
iter.next();
|
||||||
assert_eq!(iter.line(), 2);
|
assert_eq!(iter.line(), 2);
|
||||||
assert_eq!(iter.column(), 1);
|
assert_eq!(iter.column(), 1);
|
||||||
let pos_result = pos!(iter);
|
let pos_result: Result<StrIter, StrIter> = input!(iter);
|
||||||
assert!(pos_result.is_complete());
|
assert!(pos_result.is_complete());
|
||||||
if let Result::Complete(_, (line, column)) = pos_result {
|
if let Result::Complete(_, i) = pos_result {
|
||||||
assert_eq!(line, 2);
|
assert_eq!(i.line(), 2);
|
||||||
assert_eq!(column, 1);
|
assert_eq!(i.column(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_separated_good() {
|
||||||
|
let input_str = "foo,foo,foo";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = separated!(iter, text_token!(","), text_token!("foo"));
|
||||||
|
assert!(result.is_complete());
|
||||||
|
if let Result::Complete(_, list) = result {
|
||||||
|
assert_eq!(list.len(), 3);
|
||||||
|
assert_eq!(list[0], "foo");
|
||||||
|
assert_eq!(list[1], "foo");
|
||||||
|
assert_eq!(list[2], "foo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_separated_single_item() {
|
||||||
|
let input_str = "foo";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = separated!(iter, text_token!(","), text_token!("foo"));
|
||||||
|
assert!(result.is_complete());
|
||||||
|
if let Result::Complete(_, list) = result {
|
||||||
|
assert_eq!(list.len(), 1);
|
||||||
|
assert_eq!(list[0], "foo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_separated_empty_list() {
|
||||||
|
let input_str = "";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = separated!(iter, text_token!(","), text_token!("foo"));
|
||||||
|
assert!(result.is_fail());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_separated_bad() {
|
||||||
|
let input_str = "bar foo,foo";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = separated!(iter, text_token!(","), text_token!("foo"));
|
||||||
|
assert!(result.is_fail());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_separated_trailing_comma() {
|
||||||
|
let input_str = "foo,foo,foo,";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = separated!(iter, text_token!(","), text_token!("foo"));
|
||||||
|
assert!(result.is_complete());
|
||||||
|
if let Result::Complete(i, list) = result {
|
||||||
|
assert_eq!(list.len(), 3);
|
||||||
|
assert_eq!(list[0], "foo");
|
||||||
|
assert_eq!(list[1], "foo");
|
||||||
|
assert_eq!(list[2], "foo");
|
||||||
|
assert!(text_token!(i, ",").is_complete());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ascii_alphanumeric() {
|
||||||
|
let input_str = "a1";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = repeat!(iter, ascii_alphanumeric);
|
||||||
|
assert!(result.is_complete());
|
||||||
|
if let Result::Complete(i,list) = result {
|
||||||
|
assert_eq!(list.len(), 2);
|
||||||
|
assert_eq!(list[0], b'a');
|
||||||
|
assert_eq!(list[1], b'1');
|
||||||
|
assert!(eoi(i).is_complete());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ascii_alphanumeric_fail() {
|
||||||
|
let input_str = "-";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = ascii_alphanumeric(iter);
|
||||||
|
assert!(result.is_fail());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ascii_digit() {
|
||||||
|
let input_str = "12";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = repeat!(iter, ascii_digit);
|
||||||
|
assert!(result.is_complete());
|
||||||
|
if let Result::Complete(i,list) = result {
|
||||||
|
assert_eq!(list.len(), 2);
|
||||||
|
assert_eq!(list[0], b'1');
|
||||||
|
assert_eq!(list[1], b'2');
|
||||||
|
assert!(eoi(i).is_complete());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ascii_digit_fail() {
|
||||||
|
let input_str = "-";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = ascii_digit(iter);
|
||||||
|
assert!(result.is_fail());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ascii_alpha() {
|
||||||
|
let input_str = "ab";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = repeat!(iter, ascii_alpha);
|
||||||
|
assert!(result.is_complete());
|
||||||
|
if let Result::Complete(i,list) = result {
|
||||||
|
assert_eq!(list.len(), 2);
|
||||||
|
assert_eq!(list[0], b'a');
|
||||||
|
assert_eq!(list[1], b'b');
|
||||||
|
assert!(eoi(i).is_complete());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ascii_alpha_fail() {
|
||||||
|
let input_str = "1";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = ascii_alpha(iter);
|
||||||
|
assert!(result.is_fail());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_consume_all() {
|
||||||
|
let input_str = "foo;";
|
||||||
|
let iter = StrIter::new(input_str);
|
||||||
|
let result = consume_all!(iter, ascii_alpha);
|
||||||
|
assert!(result.is_complete());
|
||||||
|
if let Result::Complete(i, o) = result {
|
||||||
|
assert_eq!(i.get_offset(), 3);
|
||||||
|
assert_eq!(o, "foo");
|
||||||
|
assert!(text_token!(i, ";").is_complete());
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user