protobuf-simple-0.1.1.1: src/Parser/ProtoParser.hs
-- |
-- Module: Parser.ProtoParser
-- Copyright: (c) 2015-2016 Martijn Rijkeboer <mrr@sru-systems.com>
-- License: MIT
-- Maintainer: Martijn Rijkeboer <mrr@sru-systems.com>
--
-- Parser for proto files.
module Parser.ProtoParser
( parseProto
) where
import Control.Monad (void)
import Data.Functor (($>))
import Data.Int (Int32)
import Parser.EnumDesc (EnumDesc)
import Parser.FieldDesc (FieldDesc)
import Parser.FileDesc (FileDesc)
import Parser.Label (Label)
import Parser.MessageDesc (MessageDesc)
import Parser.OptimizeMode (OptimizeMode)
import System.FilePath (dropExtensions, splitFileName)
import Text.Parsec
( anyChar,
char,
digit,
hexDigit,
letter,
noneOf,
octDigit,
oneOf,
string,
choice,
eof,
many1,
manyTill,
option,
(<?>),
(<|>),
lookAhead,
many,
parse,
try,
Parsec
)
import qualified Parser.CaseUtils as CU
import qualified Parser.EnumDesc as EnumDesc
import qualified Parser.EnumValueDesc as EnumValueDesc
import qualified Parser.FieldDesc as FieldDesc
import qualified Parser.FileDesc as FileDesc
import qualified Parser.Label as Label
import qualified Parser.MessageDesc as MessageDesc
import qualified Parser.OptimizeMode as OptimizeMode
parseProto :: String -> String -> Either String FileDesc
parseProto filePath input =
case parse (file $ FileDesc.new $ getName filePath) filePath input of
Right fileDesc -> Right fileDesc
Left parseError -> Left $ show parseError
getName :: String -> String
getName = dropExtensions . snd . splitFileName
-- FileDesc functions
file :: FileDesc -> Parsec String u FileDesc
file s = (eof >> return s) <|> (choice
[ comments s
, syntax s
, package s
, fileOption s
, enumDesc s
, messageDesc s
] >>= file)
comments :: FileDesc -> Parsec String u FileDesc
comments s = do
void $ choice
[ char '/' *> choice
[ char '/' *> manyTill anyChar (void eol <|> eof)
, char '*' *> manyTill anyChar (try (string "*/"))
]
, many1 (oneOf " \t\n\t")
]
return s
syntax :: FileDesc -> Parsec String u FileDesc
syntax s = do
void $ keyword "syntax"
void $ symbol '='
void $ char '"'
void $ string "proto2"
void $ char '"'
void $ symbol ';'
return s
package :: FileDesc -> Parsec String u FileDesc
package s = do
void $ keyword "package"
val <- (CU.toDotted . fmap CU.toTitleWord . CU.fromDotted) <$> dString
void $ symbol ';'
return $ FileDesc.setPackage val s
fileOption :: FileDesc -> Parsec String u FileDesc
fileOption s = choice
[ try $ fileCcGenericServices s
, try $ fileDeprecated s
, try $ fileGoPackage s
, try $ fileJavaGenerateEqualsAndHash s
, try $ fileJavaGenericServices s
, try $ fileJavaPackage s
, try $ fileJavaOuterClassname s
, try $ fileJavaMultipleFiles s
, try $ fileJavaStringCheckUtf8 s
, try $ fileOptimizeFor s
, try $ filePyGenericServices s
]
fileCcGenericServices :: FileDesc -> Parsec String u FileDesc
fileCcGenericServices s =
getFileOption "cc_generic_services" bool >> return s
fileDeprecated :: FileDesc -> Parsec String u FileDesc
fileDeprecated s =
getFileOption "deprecated" bool >> return s
fileGoPackage :: FileDesc -> Parsec String u FileDesc
fileGoPackage s =
getFileOption "go_package" qString >> return s
fileJavaGenerateEqualsAndHash :: FileDesc -> Parsec String u FileDesc
fileJavaGenerateEqualsAndHash s =
getFileOption "java_generate_equals_and_hash" bool >> return s
fileJavaGenericServices :: FileDesc -> Parsec String u FileDesc
fileJavaGenericServices s =
getFileOption "java_generic_services" bool >> return s
fileJavaMultipleFiles :: FileDesc -> Parsec String u FileDesc
fileJavaMultipleFiles s =
getFileOption "java_multiple_files" bool >> return s
fileJavaOuterClassname :: FileDesc -> Parsec String u FileDesc
fileJavaOuterClassname s =
getFileOption "java_outer_classname" qString >> return s
fileJavaPackage :: FileDesc -> Parsec String u FileDesc
fileJavaPackage s =
getFileOption "java_package" qString >> return s
fileJavaStringCheckUtf8 :: FileDesc -> Parsec String u FileDesc
fileJavaStringCheckUtf8 s =
getFileOption "java_string_check_utf8" bool >> return s
fileOptimizeFor :: FileDesc -> Parsec String u FileDesc
fileOptimizeFor s =
getFileOption "optimize_for" optimizeMode >> return s
optimizeMode :: Parsec String u OptimizeMode
optimizeMode = choice
[ string "SPEED" $> OptimizeMode.Speed
, string "CODE_SIZE" $> OptimizeMode.CodeSize
, string "LITE_RUNTIME" $> OptimizeMode.LiteRuntime
]
filePyGenericServices :: FileDesc -> Parsec String u FileDesc
filePyGenericServices s =
getFileOption "py_generic_services" bool >> return s
getFileOption :: String -> Parsec String u t -> Parsec String u t
getFileOption name parser = do
void $ keyword "option"
void $ keyword name
void $ symbol '='
val <- parser
void $ symbol ';'
return val
-- MessageDesc functions
messageDesc :: FileDesc -> Parsec String u FileDesc
messageDesc s = do
void $ keyword "message"
name <- messageName
void $ symbol '{'
desc <- messageContent $ MessageDesc.new name
return $ FileDesc.addMessageDesc desc s
subMessageDesc :: MessageDesc -> Parsec String u MessageDesc
subMessageDesc s = do
void $ keyword "message"
name <- messageName
void $ symbol '{'
desc <- messageContent $ MessageDesc.new name
return $ MessageDesc.addMessageDesc desc s
messageContent :: MessageDesc -> Parsec String u MessageDesc
messageContent s = messageEnd s <|> (choice
[ try $ fieldDesc s
, try $ messageOption s
, try $ subMessageDesc s
, try $ subEnumDesc s
] >>= messageContent)
messageEnd :: MessageDesc -> Parsec String u MessageDesc
messageEnd s = symbol '}' >> return s
messageName :: Parsec String u String
messageName = identifier
messageOption :: MessageDesc -> Parsec String u MessageDesc
messageOption s = choice
[ try $ messageDeprecated s
, try $ messageSetWireFormat s
, try $ noStandardDescriptorAccessor s
]
messageDeprecated :: MessageDesc -> Parsec String u MessageDesc
messageDeprecated s =
getMessageOption "deprecated" bool >> return s
messageSetWireFormat :: MessageDesc -> Parsec String u MessageDesc
messageSetWireFormat s =
getMessageOption "message_set_wire_format" bool >> return s
noStandardDescriptorAccessor :: MessageDesc -> Parsec String u MessageDesc
noStandardDescriptorAccessor s =
getMessageOption "no_standard_descriptor_accessor" bool >> return s
getMessageOption :: String -> Parsec String u t -> Parsec String u t
getMessageOption name parser = do
void $ keyword "option"
void $ keyword name
void $ symbol '='
val <- parser
void $ symbol ';'
return val
-- EnumDesc functions
enumDesc :: FileDesc -> Parsec String u FileDesc
enumDesc s = do
void $ keyword "enum"
name <- enumName
void $ symbol '{'
desc <- enumContent $ EnumDesc.new name
return $ FileDesc.addEnumDesc desc s
subEnumDesc :: MessageDesc -> Parsec String u MessageDesc
subEnumDesc s = do
void $ keyword "enum"
name <- enumName
void $ symbol '{'
desc <- enumContent $ EnumDesc.new name
return $ MessageDesc.addEnumDesc desc s
enumContent :: EnumDesc -> Parsec String u EnumDesc
enumContent s = enumEnd s <|> (choice
[ try $ enumOption s
, try $ enumValueDesc s
] >>= enumContent)
enumEnd :: EnumDesc -> Parsec String u EnumDesc
enumEnd s = symbol '}' >> return s
enumName :: Parsec String u String
enumName = identifier
enumOption :: EnumDesc -> Parsec String u EnumDesc
enumOption s = choice
[ try $ enumAllowAlias s
, try $ enumDeprecated s
]
enumAllowAlias :: EnumDesc -> Parsec String u EnumDesc
enumAllowAlias s = do
val <- getEnumOption "allow_alias" bool
return $ EnumDesc.setAllowAlias val s
enumDeprecated :: EnumDesc -> Parsec String u EnumDesc
enumDeprecated s =
getEnumOption "deprecated" bool >> return s
getEnumOption :: String -> Parsec String u t -> Parsec String u t
getEnumOption name parser = do
void $ keyword "option"
void $ keyword name
void $ symbol '='
val <- parser
void $ symbol ';'
return val
enumValueDesc :: EnumDesc -> Parsec String u EnumDesc
enumValueDesc s = do
name <- enumValueName
void $ symbol '='
number <- enumValueNumber
void $ symbol ';'
return $ EnumDesc.addValueDesc (EnumValueDesc.new name number) s
enumValueName :: Parsec String u String
enumValueName = fmap (CU.toPascal . CU.fromSnake) identifier
enumValueNumber :: Parsec String u Int32
enumValueNumber = int32
-- FieldDesc functions
fieldDesc :: MessageDesc -> Parsec String u MessageDesc
fieldDesc s = do
flabel <- fieldLabel
tname <- typeName
name <- fieldName
void $ symbol '='
fnum <- fieldNumber
new <- fieldOptions (FieldDesc.new name fnum flabel tname)
void $ symbol ';'
return $ MessageDesc.addField new s
fieldLabel :: Parsec String u Label
fieldLabel = choice
[ try $ keyword "optional" $> Label.Optional
, try $ keyword "required" $> Label.Required
, try $ keyword "repeated" $> Label.Repeated
]
typeName :: Parsec String u String
typeName = identifier
fieldName :: Parsec String u String
fieldName = fmap (CU.toCamel . CU.fromSnake) identifier
fieldNumber :: Parsec String u Int32
fieldNumber = int32
fieldOptions :: FieldDesc -> Parsec String u FieldDesc
fieldOptions s = option s (do
void $ symbol '['
s' <- fieldOption s
void $ symbol ']'
return s'
)
fieldOption :: FieldDesc -> Parsec String u FieldDesc
fieldOption s = choice
[ try $ fieldDefault s
, try $ fieldDeprecated s
, try $ fieldLazy s
, try $ fieldPacked s
]
fieldDefault :: FieldDesc -> Parsec String u FieldDesc
fieldDefault s = do
val <- getFieldOption "default" defaultValue
return $ FieldDesc.setDefaultValue val s
fieldDeprecated :: FieldDesc -> Parsec String u FieldDesc
fieldDeprecated s =
getFieldOption "deprecated" bool >> return s
fieldLazy :: FieldDesc -> Parsec String u FieldDesc
fieldLazy s =
getFieldOption "lazy" bool >> return s
fieldPacked :: FieldDesc -> Parsec String u FieldDesc
fieldPacked s = do
val <- getFieldOption "packed" bool
return $ FieldDesc.setPacked val s
getFieldOption :: String -> Parsec String u t -> Parsec String u t
getFieldOption name parser = do
void $ keyword name
void $ symbol '='
parser
-- General functions
bool :: Parsec String u Bool
bool = choice
[try (keyword "FALSE") $> False
,keyword "False" $> False
,keyword "false" $> False
,try (keyword "TRUE") $> True
,keyword "True" $> True
,keyword "true" $> True
]
defaultValue :: Parsec String u String
defaultValue = lexeme (manyTill anyChar (lookAhead $ char ']'))
dot :: Parsec String u Char
dot = char '.'
dQuote :: Parsec String u Char
dQuote = lexeme $ char '"'
dString :: Parsec String u String
dString = lexeme ((:) <$> startChar <*> many nextChar)
where
startChar = letter <|> underscore
nextChar = startChar <|> digit <|> dot
eol :: Parsec String u String
eol = try (string "\n\r")
<|> try (string "\r\n")
<|> string "\n"
<|> string "\r"
<?> "end of line"
identifier :: Parsec String u String
identifier = lexeme ((:) <$> startChar <*> many nextChar)
where
startChar = letter <|> underscore
nextChar = startChar <|> digit
int32 :: Parsec String u Int32
int32 = read <$> lexeme ((:) <$> char '0' <*> zeroStart
<|> many1 digit)
where
zeroStart = choice [ (:) <$> oneOf "xX" <*> many1 hexDigit
, (:) <$> oneOf "oO" <*> many1 octDigit
, many digit
]
keyword :: String -> Parsec String u String
keyword k = lexeme $ string k
lexeme :: Parsec String u a -> Parsec String u a
lexeme p = p <* whitespace
qString :: Parsec String u String
qString = dQuote *> many (noneOf "\"") <* dQuote
symbol :: Char -> Parsec String u Char
symbol s = lexeme $ char s
underscore :: Parsec String u Char
underscore = char '_'
whitespace :: Parsec String u ()
whitespace = choice
[ whiteChar *> whitespace
, comment *> whitespace
, return ()
]
where
comment = try (string "//") *> manyTill anyChar (void eol <|> eof)
whiteChar = void $ many1 (oneOf " \t\n\r")