rust-bakery/nom

separated_listX bug with separator that's allowed to be empty

brundonsmith opened this issue · 1 comments

Nom v7.1.1

fn whitespace(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
    take_while(move |c| c == ' ')(i)
}

#[test]
fn demonstration() {
    println!("{:?}", separated_list0(whitespace, tag("abc"))("abc"));
}
Err(Error(VerboseError { errors: [("", Nom(SeparatedList))] }))

whitespace succeeds on its own against an empty input, but when used with separated_list0 and at the very end of the input, an infinite-loop is falsely detected and an error is returned. Instead, I think we should wait to check for no-progress until after the item's parser has also been run:

pub fn separated_list0<I, O, O2, E, F, G>(
  mut sep: G,
  mut f: F,
) -> impl FnMut(I) -> IResult<I, Vec<O>, E>
where
  I: Clone + InputLength,
  F: Parser<I, O, E>,
  G: Parser<I, O2, E>,
  E: ParseError<I>,
{
  move |mut i: I| {
    let mut res = Vec::new();

    match f.parse(i.clone()) {
      Err(Err::Error(_)) => return Ok((i, res)),
      Err(e) => return Err(e),
      Ok((i1, o)) => {
        res.push(o);
        i = i1;
      }
    }

    loop {
      let len = i.input_len();
      match sep.parse(i.clone()) {
        Err(Err::Error(_)) => return Ok((i, res)),
        Err(e) => return Err(e),
        Ok((i1, _)) => {
          // infinite loop check: the parser must always consume
          // if i1.input_len() == len {
          //    return Err(Err::Error(E::from_error_kind(i1, ErrorKind::SeparatedList)));
          // }

          match f.parse(i1.clone()) {
            Err(Err::Error(_)) => return Ok((i, res)),
            Err(e) => return Err(e),
            Ok((i2, o)) => {
              // infinite loop check: the parser must always consume
              if i2.input_len() == len {
                return Err(Err::Error(E::from_error_kind(i2, ErrorKind::SeparatedList)));
              }

              res.push(o);
              i = i2;
            }
          }
        }
      }
    }
  }
}

Any progress on this?