use std::convert::Infallible;
use nom::{
error::{append_error, ErrorKind::SeparatedNonEmptyList, FromExternalError, ParseError},
Err::{Error, Failure, Incomplete},
Parser,
};
fn make_infallible<A, B>(
mut func: impl FnMut(A, B) -> A,
) -> impl FnMut(A, B) -> Result<A, Infallible> {
move |a, b| Ok(func(a, b))
}
#[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,
fold: impl FnMut(Accum, ParseOutput) -> Accum,
) -> impl Parser<Input, Accum, ParseErr>
where
Input: Clone + PartialEq,
ParseErr: ParseError<Input>,
{
parse_separated_terminated_impl(
parser,
separator,
terminator,
init,
make_infallible(fold),
|_input, _err| unreachable!(),
)
}
#[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 + PartialEq,
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 {
None,
Item,
Separator,
}
fn maybe_attach_error<I, E: ParseError<I>>(err1: E, err2: Option<E>) -> E {
match err2 {
None => err1,
Some(err2) => err1.or(err2),
}
}
#[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 + PartialEq,
ParseErr: ParseError<Input>,
{
move |mut input: Input| {
let mut accum = init();
let mut zero_length_state = ZeroLengthParseState::None;
let mut terminator_error = None;
loop {
let (tail, value) = match parser.parse(input.clone()) {
Ok(success) => success,
Err(err) => {
break Err(err.map(move |err| {
append_error(
input,
SeparatedNonEmptyList,
maybe_attach_error(err, terminator_error),
)
}))
}
};
match (input == tail, zero_length_state) {
(true, ZeroLengthParseState::Separator) => {
break Err(Error(ParseErr::from_error_kind(
input,
SeparatedNonEmptyList,
)))
}
(true, _) => zero_length_state = ZeroLengthParseState::Item,
(false, _) => zero_length_state = ZeroLengthParseState::None,
}
accum = fold(accum, value).map_err(|err| Error(build_error(input, err)))?;
input = tail;
let term_err = match terminator.parse(input.clone()) {
Ok((tail, _)) => break Ok((tail, accum)),
Err(Error(err)) => err,
Err(err) => {
break Err(
err.map(move |err| ParseErr::append(input, SeparatedNonEmptyList, err))
)
}
};
let tail = match separator.parse(input.clone()) {
Ok((tail, _)) => tail,
Err(Error(err)) => {
break Err(Error(append_error(
input,
SeparatedNonEmptyList,
ParseErr::or(err, term_err),
)))
}
Err(Failure(err)) => {
break (Err(Failure(append_error(input, SeparatedNonEmptyList, err))))
}
Err(Incomplete(n)) => break Err(Incomplete(n)),
};
match (input == tail, zero_length_state) {
(true, ZeroLengthParseState::Item) => {
break Err(Error(ParseErr::from_error_kind(
input,
SeparatedNonEmptyList,
)))
}
(true, _) => {
zero_length_state = ZeroLengthParseState::Separator;
terminator_error = Some(term_err);
}
(false, _) => {
zero_length_state = ZeroLengthParseState::None;
terminator_error = None;
}
}
input = tail;
}
}
}
#[cfg(test)]
mod test_separated_terminated {
use cascade::cascade;
use cool_asserts::assert_matches;
use nom::{
branch::alt,
character::complete::{alpha0, char, digit1, space0},
error::ErrorKind,
Err, IResult, Parser,
};
use crate::parser_ext::ParserExt;
use crate::{
error::{BaseErrorKind, ErrorTree, Expectation, StackContext},
parse_from_str,
};
use super::parse_separated_terminated;
fn parse_number_list(input: &str) -> IResult<&str, Vec<i64>, ErrorTree<&str>> {
parse_separated_terminated(
parse_from_str(digit1),
char(',').delimited_by(space0),
char('.').preceded_by(space0),
Vec::new,
|vec, num| cascade! {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::Stack{contexts, base}) => {
assert_eq!(contexts, [("abc", StackContext::Kind(ErrorKind::SeparatedNonEmptyList))]);
assert_matches!(
*base,
ErrorTree::Base{location: "abc", kind: BaseErrorKind::Expected(Expectation::Digit)},
);
}
);
}
#[test]
fn terminator_separator_miss() {
let err = parse_number_list("10, 20 30.").unwrap_err();
assert_matches!(err, Err::Error(ErrorTree::Stack{contexts, base}) => {
assert_eq!(contexts, [(" 30.", StackContext::Kind(ErrorKind::SeparatedNonEmptyList))]);
assert_matches!(*base, ErrorTree::Alt(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();
assert_matches!(err, Err::Error(ErrorTree::Stack{contexts, base}) => {
assert_eq!(contexts, [("", StackContext::Kind(ErrorKind::SeparatedNonEmptyList))]);
assert_matches!(*base, ErrorTree::Alt(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::Stack{base, contexts}) => {
assert_eq!(contexts, [("abc.", StackContext::Kind(ErrorKind::SeparatedNonEmptyList))]);
assert_matches!(*base, 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.parse_from_str().terminated(char('.')),
space0,
char(';'),
Vec::new,
|vec, num| cascade! {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();
assert_matches!(err, Err::Error(ErrorTree::Stack{contexts, base}) => {
assert_eq!(contexts, [("abc.;", StackContext::Kind(ErrorKind::SeparatedNonEmptyList))]);
assert_matches!(*base, ErrorTree::Alt(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| cascade! {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(
parse_from_str(digit1),
char(','),
char(',').opt().all_consuming(),
Vec::new,
|vec, num| cascade! {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]),
)
}
}