Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AsByte trait and satisfy, one_of and none_of for bytes #1789

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion src/bytes/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use core::marker::PhantomData;
use crate::error::ParseError;
use crate::internal::{IResult, Parser};
use crate::traits::{Compare, FindSubstring, FindToken, ToUsize};
use crate::AsByte;
use crate::Complete;
use crate::Emit;
use crate::Input;
Expand Down Expand Up @@ -485,6 +486,75 @@ where
move |i: I| parser.process::<OutputM<Emit, Emit, Complete>>(i)
}

/// Takes 1 byte and checks that it satisfies a predicate
///
/// *Complete version*: Will return an error if there's not enough input data.
/// # Example
///
/// ```
/// # use nom::{Err, error::{ErrorKind, Error}, Needed, IResult};
/// # use nom::bytes::complete::satisfy;
/// fn parser(i: &[u8]) -> IResult<&[u8], u8> {
/// satisfy(|c| c == b'a' || c == b'b')(i)
/// }
/// assert_eq!(parser(b"abc" as &[u8]), Ok((b"bc" as &[u8], b'a')));
/// assert_eq!(parser(b"cd" as &[u8]), Err(Err::Error(Error::new(b"cd" as &[u8], ErrorKind::Satisfy))));
/// assert_eq!(parser(b"" as &[u8]), Err(Err::Error(Error::new(b"" as &[u8], ErrorKind::Satisfy))));
/// ```
pub fn satisfy<F, I, Error: ParseError<I>>(predicate: F) -> impl FnMut(I) -> IResult<I, u8, Error>
where
I: Input,
<I as Input>::Item: AsByte,
F: Fn(u8) -> bool,
{
let mut parser = super::satisfy(predicate);
move |i: I| parser.process::<OutputM<Emit, Emit, Complete>>(i)
}

/// Matches 1 byte and checks it is equal to one of the provided bytes
///
/// *Complete version*: Will return an error if there's not enough input data.
/// # Example
///
/// ```
/// # use nom::{Err, error::ErrorKind, Needed};
/// # use nom::bytes::complete::one_of;
/// assert_eq!(one_of::<_, _, (_, ErrorKind)>(b"abc" as &[u8])(b"b" as &[u8]), Ok((b"" as &[u8], b'b')));
/// assert_eq!(one_of::<_, _, (_, ErrorKind)>(b"a" as &[u8])(b"bc" as &[u8]), Err(Err::Error((b"bc" as &[u8], ErrorKind::OneOf))));
/// assert_eq!(one_of::<_, _, (_, ErrorKind)>(b"a" as &[u8])(b"" as &[u8]), Err(Err::Error((b"" as &[u8], ErrorKind::OneOf))));
/// ```
pub fn one_of<I, T, Error: ParseError<I>>(list: T) -> impl FnMut(I) -> IResult<I, u8, Error>
where
I: Input,
<I as Input>::Item: AsByte,
T: FindToken<u8>,
{
let mut parser = super::one_of(list);
move |i: I| parser.process::<OutputM<Emit, Emit, Complete>>(i)
}

/// Recognizes a byte that is not in the provided bytes.
///
/// *Complete version*: Will return an error if there's not enough input data.
/// # Example
///
/// ```
/// # use nom::{Err, error::ErrorKind, Needed};
/// # use nom::bytes::complete::none_of;
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"abc" as &[u8])(b"z" as &[u8]), Ok((b"" as &[u8], b'z')));
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"ab" as &[u8])(b"a" as &[u8]), Err(Err::Error((b"a" as &[u8], ErrorKind::NoneOf))));
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"a" as &[u8])(b"" as &[u8]), Err(Err::Error((b"" as &[u8], ErrorKind::NoneOf))));
/// ```
pub fn none_of<I, T, Error: ParseError<I>>(list: T) -> impl FnMut(I) -> IResult<I, u8, Error>
where
I: Input,
<I as Input>::Item: AsByte,
T: FindToken<u8>,
{
let mut parser = super::none_of(list);
move |i: I| parser.process::<OutputM<Emit, Emit, Complete>>(i)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -528,7 +598,12 @@ mod tests {

delimited(
char('"'),
escaped(opt(none_of(r#"\""#)), '\\', one_of(r#"\"rnt"#)),
escaped(
// Specified the none_of and one_of parsers here as this PR adds none_of and one_of for the bytes::complete module
opt(crate::character::complete::none_of(r#"\""#)),
'\\',
crate::character::complete::one_of(r#"\"rnt"#),
),
char('"'),
)
.parse(input)
Expand Down
114 changes: 114 additions & 0 deletions src/bytes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::error::ParseError;
use crate::internal::{Err, Needed, Parser};
use crate::lib::std::result::Result::*;
use crate::traits::{Compare, CompareResult};
use crate::AsByte;
use crate::AsChar;
use crate::Check;
use crate::ExtendInto;
Expand Down Expand Up @@ -1042,3 +1043,116 @@ where
}
}
}

/// Takes 1 byte and checks that it satisfies a predicate
///
/// # Example
///
/// ```
/// # use nom::{Err, error::{ErrorKind, Error}, Needed, IResult};
/// # use nom::bytes::complete::satisfy;
/// fn parser(i: &[u8]) -> IResult<&[u8], u8> {
/// satisfy(|c| c == b'a' || c == b'b')(i)
/// }
/// assert_eq!(parser(b"abc" as &[u8]), Ok((b"bc" as &[u8], b'a')));
/// assert_eq!(parser(b"cd" as &[u8]), Err(Err::Error(Error::new(b"cd" as &[u8], ErrorKind::Satisfy))));
/// assert_eq!(parser(b"" as &[u8]), Err(Err::Error(Error::new(b"" as &[u8], ErrorKind::Satisfy))));
/// ```
pub fn satisfy<F, I, Error: ParseError<I>>(
predicate: F,
) -> impl Parser<I, Output = u8, Error = Error>
where
I: Input,
<I as Input>::Item: AsByte,
F: Fn(u8) -> bool,
{
Satisfy {
predicate,
make_error: |i: I| Error::from_error_kind(i, ErrorKind::Satisfy),
}
}

/// parser implementation for [satisfy]
pub struct Satisfy<F, MakeError> {
predicate: F,
make_error: MakeError,
}

impl<I, Error: ParseError<I>, F, MakeError> Parser<I> for Satisfy<F, MakeError>
where
I: Input,
<I as Input>::Item: AsByte,
F: Fn(u8) -> bool,
MakeError: Fn(I) -> Error,
{
type Output = u8;
type Error = Error;

#[inline(always)]
fn process<OM: crate::OutputMode>(
&mut self,
i: I,
) -> crate::PResult<OM, I, Self::Output, Self::Error> {
match (i).iter_elements().next().map(|t| {
let byte = t.as_byte();
let result = (self.predicate)(byte);
(byte, result)
}) {
None => {
if OM::Incomplete::is_streaming() {
Err(Err::Incomplete(Needed::new(1)))
} else {
Err(Err::Error(OM::Error::bind(|| (self.make_error)(i))))
}
}
Some((_, false)) => Err(Err::Error(OM::Error::bind(|| (self.make_error)(i)))),
Some((byte, true)) => Ok((i.take_from(1), OM::Output::bind(|| byte.as_byte()))),
}
}
}

/// Matches one of the provided bytes
///
/// # Example
///
/// ```
/// # use nom::{Err, error::ErrorKind};
/// # use nom::bytes::complete::one_of;
/// assert_eq!(one_of::<_, _, (&[u8], ErrorKind)>(b"abc" as &[u8])(b"b" as &[u8]), Ok((b"" as &[u8], b'b')));
/// assert_eq!(one_of::<_, _, (&[u8], ErrorKind)>(b"a" as &[u8])(b"bc" as &[u8]), Err(Err::Error((b"bc" as &[u8], ErrorKind::OneOf))));
/// assert_eq!(one_of::<_, _, (&[u8], ErrorKind)>(b"a" as &[u8])(b"" as &[u8]), Err(Err::Error((b"" as &[u8], ErrorKind::OneOf))));
/// ```
pub fn one_of<I, T, Error: ParseError<I>>(list: T) -> impl Parser<I, Output = u8, Error = Error>
where
I: Input,
<I as Input>::Item: AsByte,
T: FindToken<u8>,
{
Satisfy {
predicate: move |b: u8| list.find_token(b),
make_error: move |i| Error::from_error_kind(i, ErrorKind::OneOf),
}
}

//. Recognizes a character that is not in the provided characters.
///
/// # Example
///
/// ```
/// # use nom::{Err, error::ErrorKind, Needed};
/// # use nom::bytes::streaming::none_of;
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"abc" as &[u8])(b"z" as &[u8]), Ok((b"" as &[u8], b'z')));
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"ab" as &[u8])(b"a" as &[u8]), Err(Err::Error((b"a" as &[u8], ErrorKind::NoneOf))));
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"a" as &[u8])(b"" as &[u8]), Err(Err::Incomplete(Needed::new(1))));
/// ```
pub fn none_of<I, T, Error: ParseError<I>>(list: T) -> impl Parser<I, Output = u8, Error = Error>
where
I: Input,
<I as Input>::Item: AsByte,
T: FindToken<u8>,
{
Satisfy {
predicate: move |b: u8| !list.find_token(b),
make_error: move |i| Error::from_error_kind(i, ErrorKind::NoneOf),
}
}
69 changes: 69 additions & 0 deletions src/bytes/streaming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use core::marker::PhantomData;
use crate::error::ParseError;
use crate::internal::{IResult, Parser};
use crate::traits::{Compare, FindSubstring, FindToken, ToUsize};
use crate::AsByte;
use crate::Emit;
use crate::Input;
use crate::OutputM;
Expand Down Expand Up @@ -497,3 +498,71 @@ where

move |i: I| parser.process::<OutputM<Emit, Emit, Streaming>>(i)
}

/// Takes 1 byte and checks that it satisfies a predicate
///
/// # Example
///
/// ```
/// # use nom::{Err, error::{ErrorKind, Error}, Needed, IResult};
/// # use nom::bytes::streaming::satisfy;
/// fn parser(i: &[u8]) -> IResult<&[u8], u8> {
/// satisfy(|c| c == b'a' || c == b'b')(i)
/// }
/// assert_eq!(parser(b"abc"), Ok((b"bc" as &[u8], b'a')));
/// assert_eq!(parser(b"cd"), Err(Err::Error(Error::new(b"cd" as &[u8], ErrorKind::Satisfy))));
/// assert_eq!(parser(b""), Err(Err::Incomplete(Needed::new(1))));
/// ```
pub fn satisfy<F, I, Error: ParseError<I>>(cond: F) -> impl FnMut(I) -> IResult<I, u8, Error>
where
I: Input,
<I as Input>::Item: AsByte,
F: Fn(u8) -> bool,
{
let mut parser = super::satisfy(cond);
move |i: I| parser.process::<OutputM<Emit, Emit, Streaming>>(i)
}

/// Matches 1 byte and checks it is equal to one of the provided bytes
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if there's not enough input data.
/// # Example
///
/// ```
/// # use nom::{Err, error::ErrorKind, Needed};
/// # use nom::bytes::streaming::one_of;
/// assert_eq!(one_of::<_, _, (_, ErrorKind)>(b"abc" as &[u8])(b"b" as &[u8]), Ok((b"" as &[u8], b'b')));
/// assert_eq!(one_of::<_, _, (_, ErrorKind)>(b"a" as &[u8])(b"bc" as &[u8]), Err(Err::Error((b"bc" as &[u8], ErrorKind::OneOf))));
/// assert_eq!(one_of::<_, _, (_, ErrorKind)>(b"a" as &[u8])(b"" as &[u8]), Err(Err::Incomplete(Needed::new(1))));
/// ```
pub fn one_of<I, T, Error: ParseError<I>>(list: T) -> impl FnMut(I) -> IResult<I, u8, Error>
where
I: Input,
<I as Input>::Item: AsByte,
T: FindToken<u8>,
{
let mut parser = super::one_of(list);
move |i: I| parser.process::<OutputM<Emit, Emit, Streaming>>(i)
}

/// Recognizes a byte that is not in the provided bytes.
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if there's not enough input data.
/// # Example
///
/// ```
/// # use nom::{Err, error::ErrorKind, Needed};
/// # use nom::bytes::streaming::none_of;
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"abc" as &[u8])(b"z" as &[u8]), Ok((b"" as &[u8], b'z')));
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"ab" as &[u8])(b"a" as &[u8]), Err(Err::Error((b"a" as &[u8], ErrorKind::NoneOf))));
/// assert_eq!(none_of::<_, _, (_, ErrorKind)>(b"a" as &[u8])(b"" as &[u8]), Err(Err::Incomplete(Needed::new(1))));
/// ```
pub fn none_of<I, T, Error: ParseError<I>>(list: T) -> impl FnMut(I) -> IResult<I, u8, Error>
where
I: Input,
<I as Input>::Item: AsByte,
T: FindToken<u8>,
{
let mut parser = super::none_of(list);
move |i: I| parser.process::<OutputM<Emit, Emit, Streaming>>(i)
}
20 changes: 20 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,26 @@ impl<'a> AsChar for &'a char {
}
}

/// Transforms common types to a single byte
pub trait AsByte: Copy {
/// makes a byte from self
fn as_byte(self) -> u8;
}

impl AsByte for u8 {
#[inline]
fn as_byte(self) -> u8 {
self
}
}

impl<'a> AsByte for &'a u8 {
#[inline]
fn as_byte(self) -> u8 {
*self
}
}

/// Indicates whether a comparison was successful, an error, or
/// if more data was needed
#[derive(Debug, Eq, PartialEq)]
Expand Down