haskellattoparsec

How can I use sepBy with an ending character in attoparsec?


I'm trying to parse the following line:

ItemCount count@Int: You have purchased #{showInt count} #{plural count "item" "items"}

Essentially I'm trying to get:

  Message {
      key = "ItemCount",
      value = "You have purchased #{showInt count} #{plural count \"item\" \"items\"}.",
      parameters = [Parameter {name = "count", paramType = "Int"}]
      }

The first bit ItemCount count@Int: could be:

ItemCount:

or

ItemCount count@Int count2@Int:

So the parameters could be zero or more occurrances. I'm not sure how to detect the : when using sepBy. If I were using Parsec I would look to use sepEndBy https://hackage.haskell.org/package/parsec-3.1.13.0/docs/Text-ParserCombinators-Parsec-Combinator.html#v:sepEndBy.

I'm sort of looking for sepBy untill it sees a char of :. I suppose I could first consume everything untill : and then run an 'inner' parse onn that consumed string, however it seems tedious to handle all the failure cases (Partial etc), and I'm thinking there must be a better way.

Here is my code:

import Control.Applicative
import Data.Attoparsec.Text
import Data.Text (Text)

data Parameter = Parameter { name :: Text, paramType :: Text } deriving (Eq, Show)
data Message = Message { key :: Text, value :: Text, parameters :: [Parameter] } deriving (Eq, Show)
data KeyAndParameters = KeyAndParameters Text [Parameter]

parseParameter :: Parser Parameter
parseParameter = do
  n <- takeWhile1 (notInClass "@")
  _ <- char '@'
  t <- takeWhile1 (const True)
  return $ Parameter { name = n, paramType = t }

parseKeyAndParameters :: Parser KeyAndParameters
parseKeyAndParameters = do
  keyV <- takeWhile1 (notInClass " :")
  params <- parseParameter `sepBy` (char ' ')
  return $ KeyAndParameters keyV params

Solution

  • I think I have a better understanding as to how sepBy operates. Essentially you don't have to take the 'remaining' text into consideration, as with attoparsec you don't have to consume all the output until the end (unlike Parsec).

    For example given 1+2+3=6 as input and the following:

    parseExample :: Parser ([Text], Text)
    parseExample = do
      numbers <- takeWhile1 (notInClass "=+") `sepBy` char '+'
      skip (== '=')
      total <- takeWhile1 (const True)
      return $ (numbers, total)
    

    Will return: ( [ "1" , "2" , "3" ] , "6")

    After the skip (== '=') you are free to parse the remaining input in whichever way you choose.