use core::{convert::Infallible, iter};
use nom::{
error::{ErrorKind::SeparatedNonEmptyList, FromExternalError, ParseError},
Err::Error,
InputLength, Parser,
};
pub fn collect_separated_terminated<
Input,
ParseOutput,
SepOutput,
TermOutput,
ParseErr,
Collection,
>(
parser: impl Parser<Input, ParseOutput, ParseErr>,
separator: impl Parser<Input, SepOutput, ParseErr>,
terminator: impl Parser<Input, TermOutput, ParseErr>,
) -> impl Parser<Input, Collection, ParseErr>
where
Input: Clone + InputLength,
ParseErr: ParseError<Input>,
Collection: Default + Extend<ParseOutput>,
{
parse_separated_terminated(
parser,
separator,
terminator,
Collection::default,
|collection, item| express!(collection.extend(iter::once(item))),
)
}
#[inline]
pub fn parse_separated_terminated<Input, ParseOutput, SepOutput, TermOutput, ParseErr, Accum>(
parser: impl Parser<Input, ParseOutput, ParseErr>,
separator: impl Parser<Input, SepOutput, ParseErr>,
terminator: impl Parser<Input, TermOutput, ParseErr>,
init: impl FnMut() -> Accum,
mut fold: impl FnMut(Accum, ParseOutput) -> Accum,
) -> impl Parser<Input, Accum, ParseErr>
where
Input: Clone + InputLength,
ParseErr: ParseError<Input>,
{
parse_separated_terminated_impl(
parser,
separator,
terminator,
init,
move |accum, item| Ok(fold(accum, item)),
|_input, err: Infallible| match err {},
)
}
#[inline]
pub fn parse_separated_terminated_res<
Input,
ParseOutput,
SepOutput,
TermOutput,
ParseErr,
Accum,
FoldErr,
>(
parser: impl Parser<Input, ParseOutput, ParseErr>,
separator: impl Parser<Input, SepOutput, ParseErr>,
terminator: impl Parser<Input, TermOutput, ParseErr>,
init: impl FnMut() -> Accum,
fold: impl FnMut(Accum, ParseOutput) -> Result<Accum, FoldErr>,
) -> impl Parser<Input, Accum, ParseErr>
where
Input: Clone + InputLength,
ParseErr: ParseError<Input> + FromExternalError<Input, FoldErr>,
{
parse_separated_terminated_impl(parser, separator, terminator, init, fold, |input, err| {
ParseErr::from_external_error(input, SeparatedNonEmptyList, err)
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ZeroLengthParseState<E> {
None,
Item,
Separator { terminator_error: E },
}
impl<E> ZeroLengthParseState<E> {
fn terminator_error(self) -> Option<E> {
match self {
Self::Separator { terminator_error } => Some(terminator_error),
_ => None,
}
}
}
#[inline]
fn parse_separated_terminated_impl<
Input,
ParseOutput,
SepOutput,
TermOutput,
ParseErr,
Accum,
FoldErr,
>(
mut parser: impl Parser<Input, ParseOutput, ParseErr>,
mut separator: impl Parser<Input, SepOutput, ParseErr>,
mut terminator: impl Parser<Input, TermOutput, ParseErr>,
mut init: impl FnMut() -> Accum,
mut fold: impl FnMut(Accum, ParseOutput) -> Result<Accum, FoldErr>,
mut build_error: impl FnMut(Input, FoldErr) -> ParseErr,
) -> impl Parser<Input, Accum, ParseErr>
where
Input: Clone + InputLength,
ParseErr: ParseError<Input>,
{
move |mut input: Input| {
let mut accum = init();
let mut zero_length_state = ZeroLengthParseState::None;
loop {
let (tail, value) = match parser.parse(input.clone()) {
Ok(success) => success,
Err(Error(item_error)) => {
break Err(Error(match zero_length_state.terminator_error() {
None => item_error,
Some(terminator_error) => item_error.or(terminator_error),
}))
}
Err(err) => break Err(err),
};
zero_length_state = match (input.input_len() == tail.input_len(), zero_length_state) {
(true, ZeroLengthParseState::Separator { .. }) => {
break Err(Error(ParseErr::from_error_kind(
input,
SeparatedNonEmptyList,
)))
}
(true, _) => ZeroLengthParseState::Item,
(false, _) => ZeroLengthParseState::None,
};
accum = fold(accum, value).map_err(|err| Error(build_error(input, err)))?;
input = tail;
let terminator_error = match terminator.parse(input.clone()) {
Ok((tail, _)) => break Ok((tail, accum)),
Err(Error(err)) => err,
Err(err) => break Err(err),
};
let tail = match separator.parse(input.clone()) {
Ok((tail, _)) => tail,
Err(Error(separator_error)) => {
break Err(Error(separator_error.or(terminator_error)))
}
Err(err) => break Err(err),
};
zero_length_state = match (input.input_len() == tail.input_len(), zero_length_state) {
(true, ZeroLengthParseState::Item) => {
break Err(Error(ParseErr::from_error_kind(
input,
SeparatedNonEmptyList,
)))
}
(true, _) => ZeroLengthParseState::Separator { terminator_error },
(false, _) => ZeroLengthParseState::None,
};
input = tail;
}
}
}
#[cfg(test)]
mod test_separated_terminated {
use cool_asserts::assert_matches;
use nom::{
branch::alt,
character::complete::{alpha0, char, digit1, space0},
error::ErrorKind,
Err, IResult, Parser,
};
use crate::error::{BaseErrorKind, ErrorTree, Expectation};
use crate::parser_ext::ParserExt;
use super::parse_separated_terminated;
fn parse_number_list(input: &str) -> IResult<&str, Vec<i64>, ErrorTree<&str>> {
parse_separated_terminated(
digit1.parse_from_str(),
char(',').delimited_by(space0),
char('.').preceded_by(space0),
Vec::new,
|vec, num| express!(vec.push(num)),
)
.parse(input)
}
#[test]
fn basic() {
assert_eq!(
parse_number_list("1, 2, 3, 4, 5.").unwrap(),
("", vec![1, 2, 3, 4, 5]),
)
}
#[test]
fn trailing_input() {
assert_eq!(
parse_number_list("1, 2, 3, 4, 5. 4, 5, 6.").unwrap(),
(" 4, 5, 6.", vec![1, 2, 3, 4, 5]),
)
}
#[test]
fn only_one() {
assert_eq!(parse_number_list("10.").unwrap(), ("", vec![10]),)
}
#[test]
fn at_least_one() {
let err = parse_number_list("abc").unwrap_err();
assert_matches!(
err,
Err::Error(ErrorTree::Base {
location: "abc",
kind: BaseErrorKind::Expected(Expectation::Digit)
})
);
}
#[test]
fn terminator_separator_miss() {
let err = parse_number_list("10, 20 30.").unwrap_err();
let choices = assert_matches!(err, Err::Error(ErrorTree::Alt(choices)) => choices);
assert_matches!(
choices.as_slice(),
[
ErrorTree::Base {
location: "30.",
kind: BaseErrorKind::Expected(Expectation::Char(','))
},
ErrorTree::Base {
location: "30.",
kind: BaseErrorKind::Expected(Expectation::Char('.'))
},
]
);
}
#[test]
fn required_terminator() {
let err = parse_number_list("1, 2, 3").unwrap_err();
let choices = assert_matches!(err, Err::Error(ErrorTree::Alt(choices)) => choices);
assert_matches!(
choices.as_slice(),
[
ErrorTree::Base {
location: "",
kind: BaseErrorKind::Expected(Expectation::Char(','))
},
ErrorTree::Base {
location: "",
kind: BaseErrorKind::Expected(Expectation::Char('.'))
},
]
);
}
#[test]
fn item_error() {
let err = parse_number_list("1, 2, abc.").unwrap_err();
assert_matches!(
err,
Err::Error(ErrorTree::Base {
location: "abc.",
kind: BaseErrorKind::Expected(Expectation::Digit),
})
);
}
fn parse_number_dot_list(input: &str) -> IResult<&str, Vec<i64>, ErrorTree<&str>> {
parse_separated_terminated(
digit1.terminated(char('.')).parse_from_str(),
space0,
char(';'),
Vec::new,
|vec, num| express!(vec.push(num)),
)
.parse(input)
}
#[test]
fn zero_length_separator() {
assert_eq!(
parse_number_dot_list("1.2. 3.4. 5.; abc").unwrap(),
(" abc", vec![1, 2, 3, 4, 5])
);
}
#[test]
fn zero_length_separator_item_term_error() {
let err = parse_number_dot_list("1.2.3.abc.;").unwrap_err();
let choices = assert_matches!(err, Err::Error(ErrorTree::Alt(choices)) => choices);
assert_matches!(
choices.as_slice(),
[
ErrorTree::Base {
location: "abc.;",
kind: BaseErrorKind::Expected(Expectation::Digit)
},
ErrorTree::Base {
location: "abc.;",
kind: BaseErrorKind::Expected(Expectation::Char(';'))
},
]
);
}
fn parse_letters_numbers(input: &str) -> IResult<&str, Vec<&str>, ErrorTree<&str>> {
parse_separated_terminated(
alt((digit1, alpha0)),
char('-').opt(),
char(';'),
Vec::new,
|vec, num| express!(vec.push(num)),
)
.parse(input)
}
#[test]
fn zero_length_item() {
assert_eq!(
parse_letters_numbers("----; abc").unwrap(),
(" abc", vec!["", "", "", "", ""])
)
}
#[test]
fn zero_length_separators() {
assert_eq!(
parse_letters_numbers("abc123abc123; abc").unwrap(),
(" abc", vec!["abc", "123", "abc", "123"]),
)
}
#[test]
fn zero_length_mixed() {
assert_eq!(
parse_letters_numbers("abc--123abc-123-; abc").unwrap(),
(" abc", vec!["abc", "", "123", "abc", "123", ""]),
)
}
#[test]
fn infinite_loop_aborts() {
let err = parse_letters_numbers("abc123-.; abc").unwrap_err();
assert_matches!(
err,
Err::Error(ErrorTree::Base {
location: ".; abc",
kind: BaseErrorKind::Kind(ErrorKind::SeparatedNonEmptyList)
})
);
}
fn parse_comma_separated(input: &str) -> IResult<&str, Vec<i64>, ErrorTree<&str>> {
parse_separated_terminated(
digit1.parse_from_str(),
char(','),
char(',').opt().all_consuming(),
Vec::new,
|vec, num| express!(vec.push(num)),
)
.parse(input)
}
#[test]
fn empty_terminator_wins() {
assert_eq!(
parse_comma_separated("1,2,3,4").unwrap(),
("", vec![1, 2, 3, 4]),
);
}
#[test]
fn test_terminator_wins() {
assert_eq!(
parse_comma_separated("1,2,3,4,").unwrap(),
("", vec![1, 2, 3, 4]),
)
}
}