module Data.ProtoLens.TextFormat.Parser
( Message
, Field(..)
, Key(..)
, Value(..)
, parser
) where
import Data.List (intercalate)
import Data.Functor.Identity (Identity)
import Data.Text.Lazy (Text)
import Text.Parsec.Char (alphaNum, char, letter, oneOf)
import Text.Parsec.Text.Lazy (Parser)
import Text.Parsec.Combinator (eof, sepBy1, many1, choice)
import Text.Parsec.Token
import Control.Applicative ((<*), (<|>), (*>), many)
import Control.Monad (liftM, liftM2, mzero)
ptp :: GenTokenParser Text () Identity
ptp = makeTokenParser protobufLangDef
protobufLangDef :: GenLanguageDef Text () Identity
protobufLangDef = LanguageDef
{ commentStart = ""
, commentEnd = ""
, commentLine = "#"
, nestedComments = False
, identStart = letter <|> char '_'
, identLetter = alphaNum <|> oneOf "_'"
, opStart = mzero
, opLetter = mzero
, reservedNames = []
, reservedOpNames = []
, caseSensitive = True
}
type Message = [Field]
data Field = Field Key Value
deriving (Show,Ord,Eq)
data Key = Key String
| UnknownKey Integer
| ExtensionKey [String]
| UnknownExtensionKey Integer
deriving (Ord,Eq)
data Value = IntValue Integer
| DoubleValue Double
| StringValue String
| MessageValue Message
| EnumValue String
deriving (Show,Ord,Eq)
instance Show Key
where
show (Key name) = name
show (UnknownKey k) = show k
show (ExtensionKey name) = "[" ++ intercalate "." name ++ "]"
show (UnknownExtensionKey k) = "[" ++ show k ++ "]"
parser :: Parser Message
parser = whiteSpace ptp *> parseMessage <* eof
where
parseMessage = many parseField
parseField = liftM2 Field parseKey parseValue
parseKey =
liftM Key (identifier ptp) <|>
liftM UnknownKey (natural ptp) <|>
liftM ExtensionKey (brackets ptp (identifier ptp `sepBy1` dot ptp)) <|>
liftM UnknownExtensionKey (brackets ptp (natural ptp))
parseValue =
colon ptp *> choice
[parseNumber, parseString, parseEnumValue, parseMessageValue] <|>
parseMessageValue
parseNumber = do
negative <- (symbol ptp "-" >> return True) <|> return False
value <- naturalOrFloat ptp
return $ makeNumberValue negative value
parseString = liftM (StringValue . concat) . many1 $ stringLiteral ptp
parseEnumValue = liftM EnumValue (identifier ptp)
parseMessageValue = liftM MessageValue
(braces ptp parseMessage <|> angles ptp parseMessage)
makeNumberValue :: Bool -> Either Integer Double -> Value
makeNumberValue True (Left intValue) = IntValue (negate intValue)
makeNumberValue False (Left intValue) = IntValue intValue
makeNumberValue True (Right doubleValue) = DoubleValue (negate doubleValue)
makeNumberValue False (Right doubleValue) = DoubleValue doubleValue