From a2b986c0487d869f5aef1d4b19eed701f0753da2 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 28 Nov 2024 19:05:19 +0000 Subject: [PATCH] Added AsByte trait (name overlaps a little with AsBytes, maybe should rename one) Added satisfy, one_of and none_of to bytes::complete Added satisfy, one_of and none_of to bytes::streaming Ammended test for issue #1118 to specify the use of character::complete::{none_of, one_of} due to additions to bytes::complete --- src/bytes/complete.rs | 77 +++++++++++++++++++++++++++- src/bytes/mod.rs | 114 +++++++++++++++++++++++++++++++++++++++++ src/bytes/streaming.rs | 69 +++++++++++++++++++++++++ src/traits.rs | 20 ++++++++ 4 files changed, 279 insertions(+), 1 deletion(-) diff --git a/src/bytes/complete.rs b/src/bytes/complete.rs index a34897ef..c08f93bd 100644 --- a/src/bytes/complete.rs +++ b/src/bytes/complete.rs @@ -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; @@ -485,6 +486,75 @@ where move |i: I| parser.process::>(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>(predicate: F) -> impl FnMut(I) -> IResult +where + I: Input, + ::Item: AsByte, + F: Fn(u8) -> bool, +{ + let mut parser = super::satisfy(predicate); + move |i: I| parser.process::>(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>(list: T) -> impl FnMut(I) -> IResult +where + I: Input, + ::Item: AsByte, + T: FindToken, +{ + let mut parser = super::one_of(list); + move |i: I| parser.process::>(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>(list: T) -> impl FnMut(I) -> IResult +where + I: Input, + ::Item: AsByte, + T: FindToken, +{ + let mut parser = super::none_of(list); + move |i: I| parser.process::>(i) +} + #[cfg(test)] mod tests { use super::*; @@ -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) diff --git a/src/bytes/mod.rs b/src/bytes/mod.rs index b720df39..8e05ba32 100644 --- a/src/bytes/mod.rs +++ b/src/bytes/mod.rs @@ -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; @@ -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>( + predicate: F, +) -> impl Parser +where + I: 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 { + predicate: F, + make_error: MakeError, +} + +impl, F, MakeError> Parser for Satisfy +where + I: Input, + ::Item: AsByte, + F: Fn(u8) -> bool, + MakeError: Fn(I) -> Error, +{ + type Output = u8; + type Error = Error; + + #[inline(always)] + fn process( + &mut self, + i: I, + ) -> crate::PResult { + 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>(list: T) -> impl Parser +where + I: Input, + ::Item: AsByte, + T: FindToken, +{ + 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>(list: T) -> impl Parser +where + I: Input, + ::Item: AsByte, + T: FindToken, +{ + Satisfy { + predicate: move |b: u8| !list.find_token(b), + make_error: move |i| Error::from_error_kind(i, ErrorKind::NoneOf), + } +} diff --git a/src/bytes/streaming.rs b/src/bytes/streaming.rs index d7f97aae..f4285942 100644 --- a/src/bytes/streaming.rs +++ b/src/bytes/streaming.rs @@ -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; @@ -497,3 +498,71 @@ where move |i: I| parser.process::>(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>(cond: F) -> impl FnMut(I) -> IResult +where + I: Input, + ::Item: AsByte, + F: Fn(u8) -> bool, +{ + let mut parser = super::satisfy(cond); + move |i: I| parser.process::>(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>(list: T) -> impl FnMut(I) -> IResult +where + I: Input, + ::Item: AsByte, + T: FindToken, +{ + let mut parser = super::one_of(list); + move |i: I| parser.process::>(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>(list: T) -> impl FnMut(I) -> IResult +where + I: Input, + ::Item: AsByte, + T: FindToken, +{ + let mut parser = super::none_of(list); + move |i: I| parser.process::>(i) +} diff --git a/src/traits.rs b/src/traits.rs index b9b70ed6..c696adc3 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -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)]