purescript-contrib/purescript-string-parsers

Can we have more typesafety w.r.t. many-like combinators and infinite loops?

Closed this issue · 1 comments

The issue discussed in purescript-contrib/purescript-parsing#120 also applies to this project.

Paraphrasing the conclusion from the other issue:

  1. many p will get stuck in a loop if p possibly doesn't consume any input but still succeeds.
  2. We could change the semantics of many-like combinators (or add a safe variant) that doesn't hang in the situation where no input was consumed.

@jamesdbrock pointed out that

Because ParserT is a monad transformer, it's possible to imagine a ParserT s State a which consumes no input of the first parse but does consume input on the second parse, due to some mechanics of the internal state.

However, I don't think that problem applies in this project, since Parser is not a monad transformer here.

I would propose something like the following. But perhaps these safe variants could even replace the original functions.

-- Fails with parse error if parser did not consume any input
assertConsume :: forall a. Parser a -> Parser a
assertConsume p = Parser $ \posStrBefore ->
  case unParser p posStrBefore of
    Right result ->
      if posStrBefore.pos < result.suffix.pos
      then Right result
      else Left { pos: result.suffix.pos, error: "Consumed no input." }
    x -> x

safeMany :: forall a. Parser a -> Parser (List a)
safeMany = many <<< assertConsume

safeMany1 :: forall a. Parser a -> Parser (NonEmptyList a)
safeMany1 = many1 <<< assertConsume

safeManyTill :: forall a end. Parser a -> Parser end -> Parser (List a)
safeManyTill p = manyTill (assertConsume p)

safeMany1Till :: forall a end. Parser a -> Parser end -> Parser (NonEmptyList a)
safeMany1Till p = many1Till (assertConsume p)

Any thoughts?

I would like to provide a pull request to fix this. Would it be acceptable if I changed the existing many-like operators with the safe versions above?