nnolte
nnolte

Reputation: 1780

parsec: stopping at empty line

I would like to solve the following task with parsec, although splitOn "\n\n" is probably the simpler answer.
I have an inputstring like

testInput = unlines ["ab", "cd", "", "e"] -- "ab\ncd\n\ne"

The parser shall stop when encountering an empty line.

I tried this

import Text.ParserCombinators.Parsec

inputFileP :: GenParser Char st String
inputFileP = many (lower <|> delimP)

delimP :: GenParser Char st Char
delimP = do
  x <- char '\n'
  notFollowedBy (char '\n')
  return x

This fails with unexpected '\n'.
Why?
I was under the impression that many x parses x until it fails and then stops.

Upvotes: 0

Views: 262

Answers (1)

sepp2k
sepp2k

Reputation: 370112

I was under the impression that many x parses x until it fails and then stops.

This is only the case if x fails without consuming any input. If x fails after consuming input, the whole parse will fail unless there's a try somewhere (this isn't just specific to many: x <|> y would also fail in that case even if y would succeed). In your case delimP fails on the notFollowedBy (char '\n') after already consuming the first \n, so the whole parse fails.

To change this behaviour, you need to explicitly enable backtracking using try like this:

delimP = try $ do
  x <- char '\n'
  notFollowedBy (char '\n')
  return x

Alternatively, you can make it so that delimP fails without consuming any input (and thus no need for try) by making it look ahead by two characters before matching the \n:

delimP = do
  notFollowedBy (string "\n\n")
  char '\n'

Upvotes: 4

Related Questions