Skip to content

Commit

Permalink
feat: Add the from_str combinator
Browse files Browse the repository at this point in the history
  • Loading branch information
Marwes committed Aug 4, 2018
1 parent f32fe7c commit 908f9eb
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 38 deletions.
28 changes: 20 additions & 8 deletions src/lib.rs
Expand Up @@ -200,6 +200,8 @@ pub use combinator::{
};
#[doc(inline)]
pub use parser::choice::choice;
#[doc(inline)]
pub use parser::combinator::from_str;

macro_rules! static_fn {
(($($arg: pat, $arg_ty: ty),*) -> $ret: ty { $body: expr }) => { {
Expand Down Expand Up @@ -271,7 +273,7 @@ macro_rules! impl_token_parser {
/// #[macro_use]
/// extern crate combine;
/// use combine::parser::char::digit;
/// use combine::{any, choice, many1, Parser, Stream};
/// use combine::{any, choice, from_str, many1, Parser, Stream};
/// use combine::error::ParseError;
///
/// parser!{
Expand All @@ -287,8 +289,7 @@ macro_rules! impl_token_parser {
/// {
/// // The body must be a block body ( `{ <block body> }`) which ends with an expression
/// // which evaluates to a parser
/// let digits = many1(digit());
/// digits.and_then(|s: String| s.parse())
/// from_str(many1::<String, _>(digit()))
/// }
/// }
///
Expand Down Expand Up @@ -565,7 +566,6 @@ macro_rules! combine_parse_partial {
$parser.parse_mode($mode, $input, state)
}};
(($ignored:ty) $mode:ident $input:ident $state:ident $parser:block) => {

$parser.parse_mode($mode, $input, $state)
};
}
Expand Down Expand Up @@ -720,19 +720,31 @@ pub mod combinator {
}

#[doc(hidden)]
#[deprecated(since = "3.0.0", note = "Please use the `parser::char` module instead")]
#[deprecated(
since = "3.0.0",
note = "Please use the `parser::char` module instead"
)]
pub use parser::char;

#[doc(hidden)]
#[deprecated(since = "3.0.0", note = "Please use the `parser::byte` module instead")]
#[deprecated(
since = "3.0.0",
note = "Please use the `parser::byte` module instead"
)]
pub use parser::byte;

#[doc(hidden)]
#[deprecated(since = "3.0.0", note = "Please use the `parser::range` module instead")]
#[deprecated(
since = "3.0.0",
note = "Please use the `parser::range` module instead"
)]
pub use parser::range;

#[doc(hidden)]
#[deprecated(since = "3.0.0", note = "Please use the `parser::regex` module instead")]
#[deprecated(
since = "3.0.0",
note = "Please use the `parser::regex` module instead"
)]
#[cfg(feature = "regex")]
pub use parser::regex;

Expand Down
139 changes: 131 additions & 8 deletions src/parser/combinator.rs
@@ -1,13 +1,15 @@
//! Various combinators which do not fit anywhere else.

use lib::error::Error as StdError;
use lib::marker::PhantomData;
use lib::mem;
use lib::str;

use error::{ConsumedResult, Info, ParseError, Tracked};
use error::{ConsumedResult, Info, ParseError, StreamError, Tracked};
use parser::error::unexpected;
use parser::item::value;
use parser::ParseMode;
use stream::{Positioned, Resetable, Stream, StreamOnce};
use stream::{input_at_eof, Positioned, Resetable, Stream, StreamErrorFor, StreamOnce};
use Parser;

use either::Either;
Expand Down Expand Up @@ -301,18 +303,31 @@ where
M: ParseMode,
{
let position = input.position();
let checkpoint = input.checkpoint();
match self.0.parse_mode(mode, input, state) {
EmptyOk(o) => match (self.1)(o) {
Ok(o) => EmptyOk(o),
Err(err) => EmptyErr(
<Self::Input as StreamOnce>::Error::from_error(position, err.into()).into(),
),
Err(err) => {
let err = <Self::Input as StreamOnce>::Error::from_error(position, err.into());

if input.is_partial() && input_at_eof(input) {
input.reset(checkpoint);
ConsumedErr(err)
} else {
EmptyErr(err.into())
}
}
},
ConsumedOk(o) => match (self.1)(o) {
Ok(o) => ConsumedOk(o),
Err(err) => ConsumedErr(
<Self::Input as StreamOnce>::Error::from_error(position, err.into()).into(),
),
Err(err) => {
if input.is_partial() && input_at_eof(input) {
input.reset(checkpoint);
}
ConsumedErr(
<Self::Input as StreamOnce>::Error::from_error(position, err.into()).into(),
)
}
},
EmptyErr(err) => EmptyErr(err),
ConsumedErr(err) => ConsumedErr(err),
Expand Down Expand Up @@ -867,3 +882,111 @@ where
{
Lazy(p)
}

mod internal {
pub trait Sealed {}
}

use self::internal::Sealed;

pub trait StrLike: Sealed {
fn from_utf8(&self) -> Result<&str, ()>;
}

#[cfg(feature = "std")]
impl Sealed for String {}
#[cfg(feature = "std")]
impl StrLike for String {
fn from_utf8(&self) -> Result<&str, ()> {
Ok(self)
}
}

impl<'a> Sealed for &'a str {}
impl<'a> StrLike for &'a str {
fn from_utf8(&self) -> Result<&str, ()> {
Ok(*self)
}
}

impl Sealed for str {}
impl StrLike for str {
fn from_utf8(&self) -> Result<&str, ()> {
Ok(self)
}
}

#[cfg(feature = "std")]
impl Sealed for Vec<u8> {}
#[cfg(feature = "std")]
impl StrLike for Vec<u8> {
fn from_utf8(&self) -> Result<&str, ()> {
(**self).from_utf8()
}
}

impl<'a> Sealed for &'a [u8] {}
impl<'a> StrLike for &'a [u8] {
fn from_utf8(&self) -> Result<&str, ()> {
(**self).from_utf8()
}
}

impl Sealed for [u8] {}
impl StrLike for [u8] {
fn from_utf8(&self) -> Result<&str, ()> {
str::from_utf8(self).map_err(|_| ())
}
}

parser!{
pub struct FromStr;
type PartialState = P::PartialState;

/// Takes a parser that outputs a string like value (`&str`, `String`, `&[u8]` or `Vec<u8>`) and parses it
/// using `std::str::FromStr`. Errors if the output of `parser` is not UTF-8 or if
/// `FromStr::from_str` returns an error.
///
/// ```
/// # extern crate combine;
/// # use combine::parser::range;
/// # use combine::parser::repeat::many1;
/// # use combine::parser::combinator::from_str;
/// # use combine::char;
/// # use combine::*;
/// # fn main() {
/// let mut parser = from_str(many1::<String, _>(char::digit()));
/// let result = parser.parse("12345\r\n");
/// assert_eq!(result, Ok((12345i32, "\r\n")));
///
/// // Range parsers work as well
/// let mut parser = from_str(range::take_while1(|c: char| c.is_digit(10)));
/// let result = parser.parse("12345\r\n");
/// assert_eq!(result, Ok((12345i32, "\r\n")));
///
/// // As do parsers that work with bytes
/// let digits = || range::take_while1(|b: u8| b >= b'0' && b <= b'9');
/// let mut parser = from_str(range::recognize((
/// digits(),
/// byte::byte(b'.'),
/// digits(),
/// )));
/// let result = parser.parse(&b"123.45\r\n"[..]);
/// assert_eq!(result, Ok((123.45f64, &b"\r\n"[..])));
/// # }
/// ```
pub fn from_str[O, P](parser: P)(P::Input) -> O
where [
P: Parser,
P::Output: StrLike,
O: str::FromStr,
O::Err: StdError + Send + Sync + 'static
]
{
parser.and_then(|r| {
r.from_utf8()
.map_err(|_| StreamErrorFor::<P::Input>::expected_static_message("UTF-8"))
.and_then(|s| s.parse().map_err(StreamErrorFor::<P::Input>::other))
})
}
}
27 changes: 21 additions & 6 deletions src/parser/range.rs
Expand Up @@ -142,7 +142,8 @@ where
input: &mut Self::Input,
state: &mut Self::PartialState,
) -> ConsumedResult<Self::Output, Self::Input>
where M: ParseMode
where
M: ParseMode,
{
let (ref mut distance_state, ref mut child_state) = *state;

Expand Down Expand Up @@ -533,18 +534,32 @@ mod tests {
#[test]
fn take_until_range_2() {
let result = take_until_range("===").parse("if ((pointless_comparison == 3) === true) {");
assert_eq!(result, Ok(("if ((pointless_comparison == 3) ", "=== true) {")));
assert_eq!(
result,
Ok(("if ((pointless_comparison == 3) ", "=== true) {"))
);
}

#[test]
fn take_until_range_unicode_1() {
let result = take_until_range("🦀").parse("😃 Ferris the friendly rustacean 🦀 and his snake friend 🐍");
assert_eq!(result, Ok(("😃 Ferris the friendly rustacean ", "🦀 and his snake friend 🐍")));
let result = take_until_range("🦀")
.parse("😃 Ferris the friendly rustacean 🦀 and his snake friend 🐍");
assert_eq!(
result,
Ok((
"😃 Ferris the friendly rustacean ",
"🦀 and his snake friend 🐍"
))
);
}

#[test]
fn take_until_range_unicode_2() {
let result = take_until_range("⁘⁙/⁘").parse("⚙️🛠️🦀=🏎️⁘⁙⁘⁘⁙/⁘⁘⁙/⁘");
assert_eq!(result, Ok(("⚙️🛠️🦀=🏎️⁘⁙⁘", "⁘⁙/⁘⁘⁙/⁘")));
let result = take_until_range("⁘⁙/⁘")
.parse("⚙️🛠️🦀=🏎️⁘⁙⁘⁘⁙/⁘⁘⁙/⁘");
assert_eq!(
result,
Ok(("⚙️🛠️🦀=🏎️⁘⁙⁘", "⁘⁙/⁘⁘⁙/⁘"))
);
}
}
15 changes: 4 additions & 11 deletions src/stream/mod.rs
Expand Up @@ -139,8 +139,7 @@ impl<I> Stream for I
where
I: StreamOnce + Positioned + Resetable,
I::Error: ParseError<I::Item, I::Range, I::Position>,
{
}
{}

#[inline]
pub fn uncons<I>(input: &mut I) -> ConsumedResult<I::Item, I>
Expand Down Expand Up @@ -207,11 +206,7 @@ pub trait RangeStreamOnce: StreamOnce + Resetable {
/// A `RangeStream` is an extension of `Stream` which allows for zero copy parsing.
pub trait RangeStream: Stream + RangeStreamOnce {}

impl<I> RangeStream for I
where
I: RangeStreamOnce + Stream,
{
}
impl<I> RangeStream for I where I: RangeStreamOnce + Stream {}

/// A `RangeStream` which is capable of providing it's entire range.
pub trait FullRangeStream: RangeStream {
Expand Down Expand Up @@ -251,7 +246,8 @@ where
}
}

fn input_at_eof<I>(input: &mut I) -> bool
#[doc(hidden)]
pub fn input_at_eof<I>(input: &mut I) -> bool
where
I: ?Sized + Stream,
{
Expand Down Expand Up @@ -363,7 +359,6 @@ where

macro_rules! test_next {
() => {

match chars.next() {
Some(c) => {
if !f(c) {
Expand Down Expand Up @@ -476,7 +471,6 @@ where

macro_rules! check {
() => {

if !f(unsafe { slice.get_unchecked(i).clone() }) {
found = true;
break;
Expand Down Expand Up @@ -757,7 +751,6 @@ where

macro_rules! check {
() => {

if !f(unsafe { slice.get_unchecked(i) }) {
found = true;
break;
Expand Down
10 changes: 5 additions & 5 deletions tests/async.rs
Expand Up @@ -25,8 +25,8 @@ use futures::{Future, Stream};
use tokio_codec::{Decoder, FramedRead};

use combine::combinator::{
any_partial_state, any_send_partial_state, no_partial, optional, recognize, skip_many1, try,
AnyPartialState, AnySendPartialState,
any_partial_state, any_send_partial_state, from_str, no_partial, optional, recognize,
skip_many1, try, AnyPartialState, AnySendPartialState,
};
use combine::error::{ParseError, StreamError};
use combine::parser::char::{char, digit, letter};
Expand Down Expand Up @@ -172,10 +172,10 @@ parser!{
fn prefix_many_then_parser['a, I]()(I) -> String
where [ I: RangeStream<Item = char, Range = &'a str> ]
{
any_partial_state((char('#'), skip_many(char(' ')), many1(digit()))
let integer = from_str(many1::<String, _>(digit()));
any_partial_state((char('#'), skip_many(char(' ')), integer)
.then_partial(|t| {
let s: &String = &t.2;
let c = s.parse().unwrap();
let c = t.2;
count_min_max(c, c, any())
})
)
Expand Down

0 comments on commit 908f9eb

Please sign in to comment.