Sergio de Carvalho
Sergio de Carvalho

Reputation: 53

How to combine parsers up to n times in Haskell?

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

Answers (1)

sshine
sshine

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

Related Questions