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
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.