Initial commit
This commit is contained in:
@ -0,0 +1,162 @@
|
||||
#[cfg(feature = "nom")]
|
||||
use {
|
||||
nom::{
|
||||
bytes::complete::take_while_m_n,
|
||||
character::complete::{char, multispace0},
|
||||
combinator::eof,
|
||||
sequence::{delimited, terminated},
|
||||
IResult, InputIter, InputLength, InputTake, Slice,
|
||||
},
|
||||
std::ops::RangeFrom,
|
||||
};
|
||||
|
||||
// TODO generic input
|
||||
|
||||
/// Trim leading and trailing whitespace from the input Parser
|
||||
/// - Parameters
|
||||
/// - `inner`: The parser to trim
|
||||
/// - Returns: A parser that trims leading and trailing whitespace from the input and then runs the value from the inner parser
|
||||
#[cfg(feature = "nom")]
|
||||
pub fn trim<'a, Parser, R>(inner: Parser) -> impl FnMut(&'a str) -> IResult<&'a str, R>
|
||||
where
|
||||
Parser: Fn(&'a str) -> IResult<&'a str, R>,
|
||||
{
|
||||
delimited(multispace0, inner, multispace0)
|
||||
}
|
||||
|
||||
/// Parse a parenthesized expression. This parser will parse an expression that is surrounded by parentheses
|
||||
/// and will trim the whitespace surrounding the expression.
|
||||
/// - Parameters
|
||||
/// - `inner`: The parser to run inside the parentheses
|
||||
/// - Returns: A parser that parses a parenthesized expression
|
||||
#[cfg(feature = "nom")]
|
||||
pub fn parenthesized<'a, Parser, R>(inner: Parser) -> impl FnMut(&'a str) -> IResult<&'a str, R>
|
||||
where
|
||||
Parser: Fn(&'a str) -> IResult<&'a str, R>,
|
||||
{
|
||||
delimited(char('('), trim(inner), char(')'))
|
||||
}
|
||||
|
||||
/// Take where the predicate is true and the length is exactly `n`
|
||||
/// - Parameters
|
||||
/// - `n`: The length of the string to take
|
||||
/// - `predicate`: The predicate to call to validate the input
|
||||
/// - Returns: A parser that takes `n` characters from the input
|
||||
#[cfg(feature = "nom")]
|
||||
pub fn take_where<F, Input>(n: usize, predicate: F) -> impl Fn(Input) -> IResult<Input, Input>
|
||||
where
|
||||
Input: InputTake + InputIter + InputLength + Slice<RangeFrom<usize>>,
|
||||
F: Fn(<Input as InputIter>::Item) -> bool + Copy,
|
||||
{
|
||||
take_while_m_n(n, n, predicate)
|
||||
}
|
||||
|
||||
#[cfg(feature = "nom")]
|
||||
pub fn exhausted<'a, Parser, R>(inner: Parser) -> impl FnMut(&'a str) -> IResult<&'a str, R>
|
||||
where
|
||||
Parser: Fn(&'a str) -> IResult<&'a str, R>,
|
||||
{
|
||||
terminated(inner, eof)
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "nom"))]
|
||||
mod tests {
|
||||
use nom::bytes::streaming::take_while;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_trim_both_sides() {
|
||||
let input = " test ";
|
||||
let (remaining, result) =
|
||||
trim(take_where(4, |c: char| c.is_ascii_alphabetic()))(input).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
assert_eq!(result, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim_leading() {
|
||||
let input = " test";
|
||||
let (remaining, result) =
|
||||
trim(take_where(4, |c: char| c.is_ascii_alphabetic()))(input).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
assert_eq!(result, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim_trailing() {
|
||||
let input = "test ";
|
||||
let (remaining, result) =
|
||||
trim(take_where(4, |c: char| c.is_ascii_alphabetic()))(input).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
assert_eq!(result, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim_no_trim() {
|
||||
let input = "test";
|
||||
let (remaining, result) =
|
||||
trim(take_where(4, |c: char| c.is_ascii_alphabetic()))(input).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
assert_eq!(result, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parenthesized() {
|
||||
let input = "(test)";
|
||||
let (remaining, result) =
|
||||
parenthesized(take_where(4, |c: char| c.is_ascii_alphabetic()))(input).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
assert_eq!(result, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parenthesized_parse_until_end() {
|
||||
let input = "(test)";
|
||||
assert!(parenthesized(take_while(|_| true))(input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_where() {
|
||||
let input = "test";
|
||||
let (remaining, result) = take_where(4, |c: char| c.is_ascii_alphabetic())(input).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
assert_eq!(result, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_where_not_enough() {
|
||||
let input = "tes";
|
||||
assert!(take_where(4, |c: char| c.is_ascii_alphabetic())(input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_where_too_much() {
|
||||
let input = "testing";
|
||||
assert_eq!(
|
||||
take_where(4, |c: char| c.is_ascii_alphabetic())(input),
|
||||
Ok(("ing", "test"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_where_predicate_false() {
|
||||
let input = "test";
|
||||
assert!(take_where(4, |c: char| c.is_ascii_digit())(input).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exhausted() {
|
||||
let input = "test";
|
||||
let (remaining, result) =
|
||||
exhausted(take_where(4, |c: char| c.is_ascii_alphabetic()))(input).unwrap();
|
||||
assert_eq!(remaining, "");
|
||||
assert_eq!(result, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exhausted_not_exhausted() {
|
||||
let input = "test ";
|
||||
assert!(exhausted(take_where(4, |c: char| c.is_ascii_alphabetic()))(input).is_err());
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod parser;
|
||||
pub mod combinators;
|
||||
pub mod util;
|
||||
|
@ -0,0 +1,44 @@
|
||||
#[cfg(feature = "nom")]
|
||||
use nom::{error::Error, IResult};
|
||||
|
||||
#[cfg(feature = "nom")]
|
||||
pub trait IntoResult<T> {
|
||||
type Error;
|
||||
fn into_result(self) -> Result<T, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "nom")]
|
||||
impl<T, R> IntoResult<T> for IResult<R, T> {
|
||||
type Error = nom::Err<Error<R>>;
|
||||
fn into_result(self) -> Result<T, Self::Error> {
|
||||
self.map(|(_remaining, value)| value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "nom"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nom::character::complete::char as c;
|
||||
|
||||
fn parse_char(input: &str) -> IResult<&str, char> {
|
||||
c('A')(input)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_result() {
|
||||
let i_result = parse_char("ABC");
|
||||
assert_eq!(i_result.into_result(), Ok('A'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_result_error() {
|
||||
let i_result = parse_char("BC");
|
||||
assert_eq!(
|
||||
i_result.into_result(),
|
||||
Err(nom::Err::Error(Error::new(
|
||||
"BC",
|
||||
nom::error::ErrorKind::Char
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user