Clean up implementation of list or entry parsing, add error generics

master
Nick Krichevsky 2022-01-24 23:59:57 -05:00
parent ec0783ef8e
commit 3c4d34e484
4 changed files with 88 additions and 79 deletions

View File

@ -106,15 +106,15 @@ fn perform_entry_parse(entry: &str) -> IResult<&str, CronEntry> {
let (remaining, specifiers) = separated_five_tuple(
char(' '),
// minute
parse_common_specifier,
list_or_single_specifier(parse_common_specifier),
// hour
parse_common_specifier,
list_or_single_specifier(parse_common_specifier),
// day of month
parse_common_specifier,
list_or_single_specifier(parse_common_specifier),
// month
parse_month_specifier,
list_or_single_specifier(parse_month_specifier),
// day of week
parse_day_of_week_specifier,
list_or_single_specifier(parse_day_of_week_specifier),
)(entry)?;
let parsed_entry = CronEntry {
@ -134,76 +134,41 @@ fn perform_entry_parse(entry: &str) -> IResult<&str, CronEntry> {
}
fn parse_month_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
// TODO: there's a lot of repetition right here I don't want to clean up this second,
// between this and the other callsites of parse_list_or_element
parse_list_or_element(
parse_month_list_element_specifier,
alt((
parse_month_list_element_specifier,
simple::parse_star_specifier,
)),
)(chunk)
}
fn parse_month_list_element_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
alt((
compound::parse_month_range_specifier,
word::parse_month,
parse_list_element_common_specifier,
parse_common_specifier,
))(chunk)
}
fn parse_day_of_week_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
parse_list_or_element(
parse_day_of_week_list_element_specifier,
alt((
parse_day_of_week_list_element_specifier,
simple::parse_star_specifier,
)),
)(chunk)
}
fn parse_day_of_week_list_element_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
alt((
compound::parse_day_of_week_range_specifier,
word::parse_day_of_week,
parse_list_element_common_specifier,
parse_common_specifier,
))(chunk)
}
fn parse_common_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
parse_list_or_element(
parse_list_element_common_specifier,
parse_standalone_common_specifier,
)(chunk)
}
fn parse_standalone_common_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
alt((
parse_list_element_common_specifier,
simple::parse_star_specifier,
))(chunk)
}
fn parse_list_element_common_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
alt((
compound::parse_numeric_range_specifier,
simple::parse_numeric_specifier,
))(chunk)
}
fn parse_list_or_element<'a, PL, PB, E>(
list_element_parser: PL,
base_parser: PB,
/// Make a parser that will parse either a list of specifiers, or a single one. Each list element must match
/// the given parser. A single specifier must either match this parser, or be *
fn list_or_single_specifier<'a, P, E>(
list_element_parser: P,
) -> impl FnMut(&'a str) -> IResult<&'a str, CronSpecifier, E>
where
PL: Parser<&'a str, CronSpecifier, E> + Copy,
PB: Parser<&'a str, CronSpecifier, E>,
P: Parser<&'a str, CronSpecifier, E> + Copy,
E: nom::error::ParseError<&'a str>,
{
alt((
compound::make_several_specifier_parser(list_element_parser),
base_parser,
compound::several_specifiers(list_element_parser),
list_element_parser,
simple::parse_star_specifier,
))
}

View File

@ -13,7 +13,7 @@ use std::ops::RangeFrom;
/// Make a parser that will parse a list specifier in a cron entry. Each part of the list must be matched by
/// `component_parser`
pub(super) fn make_several_specifier_parser<P, I, E>(
pub(super) fn several_specifiers<P, I, E>(
component_parser: P,
) -> impl FnMut(I) -> IResult<I, CronSpecifier, E>
where
@ -42,13 +42,25 @@ where
}
/// Parse a simple numeric range specifier, such as 5-10.
pub(super) fn parse_numeric_range_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
pub(super) fn parse_numeric_range_specifier<'a, E>(
chunk: &'a str,
) -> IResult<&'a str, CronSpecifier, E>
where
E: nom::error::ParseError<&'a str>
+ nom::error::FromExternalError<&'a str, std::num::ParseIntError>,
{
build_range_parser(simple::parse_number, simple::parse_number)(chunk)
}
/// Parse a day of week range specifier, with the left/right of the ranges either being the numeric
/// versions of a day of the week, their word forms, or a combination of both.
pub(super) fn parse_day_of_week_range_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
pub(super) fn parse_day_of_week_range_specifier<'a, E>(
chunk: &'a str,
) -> IResult<&'a str, CronSpecifier, E>
where
E: nom::error::ParseError<&'a str>
+ nom::error::FromExternalError<&'a str, std::num::ParseIntError>,
{
build_range_parser(
parse_day_of_week_range_branch,
parse_day_of_week_range_branch,
@ -57,18 +69,32 @@ pub(super) fn parse_day_of_week_range_specifier(chunk: &str) -> IResult<&str, Cr
/// Parse a month range specifier, with the left/right of the ranges either being the numeric
/// versions of a month, their word forms, or a combination of both.
pub(super) fn parse_month_range_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
pub(super) fn parse_month_range_specifier<'a, E>(
chunk: &'a str,
) -> IResult<&'a str, CronSpecifier, E>
where
E: nom::error::ParseError<&'a str>
+ nom::error::FromExternalError<&'a str, std::num::ParseIntError>,
{
build_range_parser(parse_month_range_branch, parse_month_range_branch)(chunk)
}
// unfortunately, these branch functions must be broken out into their own functions,
// as in order for `build_range_parser` to work, need `Copy` types, which the impl that
// alt returns is not.
fn parse_day_of_week_range_branch(chunk: &str) -> IResult<&str, u8> {
fn parse_day_of_week_range_branch<'a, E>(chunk: &'a str) -> IResult<&'a str, u8, E>
where
E: nom::error::ParseError<&'a str>
+ nom::error::FromExternalError<&'a str, std::num::ParseIntError>,
{
alt((word::parse_day_of_week_raw, simple::parse_number))(chunk)
}
fn parse_month_range_branch(chunk: &str) -> IResult<&str, u8> {
fn parse_month_range_branch<'a, E>(chunk: &'a str) -> IResult<&'a str, u8, E>
where
E: nom::error::ParseError<&'a str>
+ nom::error::FromExternalError<&'a str, std::num::ParseIntError>,
{
alt((word::parse_month_raw, simple::parse_number))(chunk)
}

View File

@ -4,24 +4,31 @@
use nom::{
character::complete::{char, digit1},
combinator::map_res,
error::FromExternalError,
IResult,
};
use crate::CronSpecifier;
use std::str::FromStr;
/// Parse a raw number, which can be later used to form a specifier.
pub(super) fn parse_number(chunk: &str) -> IResult<&str, u8> {
pub(super) fn parse_number<'a, E>(chunk: &'a str) -> IResult<&'a str, u8, E>
where
E: nom::error::ParseError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>,
{
map_res(digit1, str::parse)(chunk)
}
pub(super) fn parse_numeric_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
map_res::<_, _, _, _, <u8 as FromStr>::Err, _, _>(parse_number, |num: u8| {
Ok(CronSpecifier::Specifically(num))
})(chunk)
pub(super) fn parse_numeric_specifier<'a, E>(chunk: &'a str) -> IResult<&'a str, CronSpecifier, E>
where
E: nom::error::ParseError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>,
{
map_res(parse_number, |num: u8| Ok(CronSpecifier::Specifically(num)))(chunk)
}
pub(super) fn parse_star_specifier(chunk: &str) -> IResult<&str, CronSpecifier> {
pub(super) fn parse_star_specifier<'a, E>(chunk: &'a str) -> IResult<&'a str, CronSpecifier, E>
where
E: nom::error::ParseError<&'a str>,
{
let (remaining, _) = char('*')(chunk)?;
Ok((remaining, CronSpecifier::Any))

View File

@ -12,16 +12,12 @@ use crate::CronSpecifier;
/// `ParserIterator` exists as an implementation of nom's [`nom::branch::Alt`] trait so that we can use arbitrary
/// iterators for [`nom::branch::alt`].
struct ParserIterator<'a, Iter, I, O, E>(Iter)
where
I: 'a,
O: 'a,
E: 'a,
Iter: Iterator<Item = &'a mut dyn nom::Parser<I, O, E>>;
struct ParserIterator<Iter>(Iter);
impl<'a, Iter, I, O, E> Alt<I, O, E> for ParserIterator<'a, Iter, I, O, E>
impl<'a, Iter, I, O, E> Alt<I, O, E> for ParserIterator<Iter>
where
I: Clone + 'a,
O: 'a,
E: nom::error::ParseError<I> + 'a,
Iter: Iterator<Item = &'a mut dyn nom::Parser<I, O, E>>,
{
@ -57,7 +53,10 @@ where
/// Parse a day of the week, which is a case-insensitive string of the first three letters
/// of any given day of the week (e.g. tue). This "raw" form converts to a number directly,
/// rather than wrapping it in a specifier.
pub(super) fn parse_day_of_week_raw(chunk: &str) -> IResult<&str, u8> {
pub(super) fn parse_day_of_week_raw<'a, E>(chunk: &'a str) -> IResult<&'a str, u8, E>
where
E: nom::error::ParseError<&'a str>,
{
parse_possible_word_set(
chunk,
&["sun", "mon", "tue", "wed", "thu", "fri", "sat", "sun"],
@ -66,7 +65,10 @@ pub(super) fn parse_day_of_week_raw(chunk: &str) -> IResult<&str, u8> {
/// Parse a day of the week, which is a case-insensitive string of the first three letters
/// of any given day of the week (e.g. tue).
pub(super) fn parse_day_of_week(chunk: &str) -> IResult<&str, CronSpecifier> {
pub(super) fn parse_day_of_week<'a, E>(chunk: &'a str) -> IResult<&'a str, CronSpecifier, E>
where
E: nom::error::ParseError<&'a str>,
{
let (remaining, raw_specifier) = parse_day_of_week_raw(chunk)?;
Ok((remaining, CronSpecifier::Specifically(raw_specifier)))
@ -75,7 +77,10 @@ pub(super) fn parse_day_of_week(chunk: &str) -> IResult<&str, CronSpecifier> {
/// Parse a month, which is a case-insensitive string of the first three letters
/// of any given month (e.g. tue). This "raw" form converts to a number directly,
/// rather than wrapping it in a specifier.
pub(super) fn parse_month_raw(chunk: &str) -> IResult<&str, u8> {
pub(super) fn parse_month_raw<'a, E>(chunk: &'a str) -> IResult<&'a str, u8, E>
where
E: nom::error::ParseError<&'a str>,
{
parse_possible_word_set(
chunk,
&[
@ -93,7 +98,10 @@ pub(super) fn parse_month_raw(chunk: &str) -> IResult<&str, u8> {
/// Parse a month, which is a case-insensitive string of the first three letters
/// of any given month (e.g. tue).
pub(super) fn parse_month(chunk: &str) -> IResult<&str, CronSpecifier> {
pub(super) fn parse_month<'a, E>(chunk: &'a str) -> IResult<&'a str, CronSpecifier, E>
where
E: nom::error::ParseError<&'a str>,
{
let (remaining, raw_specifier) = parse_month_raw(chunk)?;
Ok((remaining, CronSpecifier::Specifically(raw_specifier)))
@ -103,10 +111,13 @@ pub(super) fn parse_month(chunk: &str) -> IResult<&str, CronSpecifier> {
/// the numeric index of the element that was matched. Because this returns a u8, it
/// is a programmer error to pass any slice of length > 255 to this method, and as such,
/// it will panic.
fn parse_possible_word_set<'a>(
fn parse_possible_word_set<'a, E>(
chunk: &'a str,
possibilities: &'a [&'a str],
) -> IResult<&'a str, u8> {
) -> IResult<&'a str, u8, E>
where
E: nom::error::ParseError<&'a str>,
{
// It is a propgrammer error to use a longer slice here; we must be able to enumerate these items into a u8
assert!(possibilities.len() <= 255);
@ -121,7 +132,7 @@ fn parse_possible_word_set<'a>(
let parser_refs = parsers
.iter_mut()
.map(|parser| parser as &mut dyn nom::Parser<&str, &str, nom::error::Error<&str>>);
.map(|parser| parser as &mut dyn nom::Parser<&str, &str, E>);
let (remaining, candidate) = alt(ParserIterator(parser_refs))(chunk)?;
@ -147,7 +158,7 @@ mod tests {
// to produce a ParserSlice
#[test]
fn test_alt_takes_first_matching_parser() {
let res = alt(ParserIterator::<_, _, _, nom::error::Error<_>>(
let res = alt(ParserIterator(
[tag("abc"), tag("def"), tag("efg")]
.iter_mut()
.map(|x| x as &mut dyn nom::Parser<&str, &str, nom::error::Error<&str>>),
@ -159,7 +170,7 @@ mod tests {
#[test]
fn test_alt_will_fail_if_no_parsers_match() {
let res = alt(ParserIterator::<_, _, _, nom::error::Error<_>>(
let res = alt(ParserIterator(
[tag("abc"), tag("def"), tag("efg")]
.iter_mut()
.map(|x| x as &mut dyn nom::Parser<&str, &str, nom::error::Error<&str>>),