Reputation: 53
I'm trying to combine parsers in Haskell in such a way that I could parse certain patterns up to n times. To illustrate, imagine I want to parse up to eight digits from the input. I know I can use count
from Text.Parser.Combinators
to parse exactly n occurrences, e.g.:
import Text.Parser.Char (digit)
import Text.Parser.Combinators (count)
eightDigits :: Parser [Char]
eightDigits = count 8 digit
This, however, fails if it doesn't find exactly 8 digits. I could also use some
to parse one or more digits:
import Text.Parser.Char (digit)
import Text.Parser.Combinators (some)
someDigits :: Parser [Char]
someDigits = some digit
The problem with the above is that it may consume more digits than I want. Finally, I could use try
, which combine parsers that may consume input and, on failure, go back to where it started:
import Text.Parser.Char (digit)
import Text.Parser.Combinators (count, try)
import Control.Applicative ((<|>))
twoOrThreeDigits :: Parser [Char]
twoOrThreeDigits = try (count 3 digit) <|> count 2 digit
While this could be extended to up to 8 repetitions, it's not scalable nor elegant, so the question is how can I combine parsers to parse a pattern anywhere between 1 and up to n times?
Upvotes: 4
Views: 1258
Reputation: 16105
You could construct a many
-like combinator with an upper limit:
upto :: Int -> Parser a -> Parser [a]
upto n p | n > 0 = (:) <$> try p <*> upto (n-1) p <|> return []
upto _ _ = return []
And for 1 up to n, a many1
-like combinator:
upto1 :: Int -> Parser a -> Parser [a]
upto1 n p | n > 0 = (:) <$> p <*> upto (n-1) p
upto1 _ _ = return []
A short demo:
> map (parse (upto 8 digitChar) "") ["", "123", "1234567890"]
[Right "",Right "123",Right "12345678"]
Upvotes: 6