protocol-buffers-0.0.5: Text/ProtocolBuffers/ParseProto.hs
module Text.ProtocolBuffers.ParseProto where
-- see compiler/parser.cc in the google source code
import qualified Text.DescriptorProtos.DescriptorProto as D(DescriptorProto)
import qualified Text.DescriptorProtos.DescriptorProto as D.DescriptorProto(DescriptorProto(..))
import qualified Text.DescriptorProtos.DescriptorProto.ExtensionRange as D.DescriptorProto(ExtensionRange)
import qualified Text.DescriptorProtos.DescriptorProto.ExtensionRange as D.DescriptorProto.ExtensionRange(ExtensionRange(..))
import qualified Text.DescriptorProtos.EnumDescriptorProto as D(EnumDescriptorProto)
import qualified Text.DescriptorProtos.EnumDescriptorProto as D.EnumDescriptorProto(EnumDescriptorProto(..))
import qualified Text.DescriptorProtos.EnumOptions as D(EnumOptions)
import qualified Text.DescriptorProtos.EnumOptions as D.EnumOptions(EnumOptions(..))
import qualified Text.DescriptorProtos.EnumValueDescriptorProto as D(EnumValueDescriptorProto)
import qualified Text.DescriptorProtos.EnumValueDescriptorProto as D.EnumValueDescriptorProto(EnumValueDescriptorProto(..))
import qualified Text.DescriptorProtos.EnumValueOptions as D(EnumValueOptions)
import qualified Text.DescriptorProtos.EnumValueOptions as D.EnumValueOptions(EnumValueOptions(..))
import qualified Text.DescriptorProtos.FieldDescriptorProto as D(FieldDescriptorProto)
import qualified Text.DescriptorProtos.FieldDescriptorProto as D.FieldDescriptorProto(FieldDescriptorProto(..))
import qualified Text.DescriptorProtos.FieldDescriptorProto.Label as D.FieldDescriptorProto(Label)
import qualified Text.DescriptorProtos.FieldDescriptorProto.Label as D.FieldDescriptorProto.Label(Label(..))
import qualified Text.DescriptorProtos.FieldDescriptorProto.Type as D.FieldDescriptorProto(Type)
import qualified Text.DescriptorProtos.FieldDescriptorProto.Type as D.FieldDescriptorProto.Type(Type(..))
import qualified Text.DescriptorProtos.FieldOptions as D(FieldOptions)
import qualified Text.DescriptorProtos.FieldOptions as D.FieldOptions(FieldOptions(..))
import qualified Text.DescriptorProtos.FieldOptions.CType as D.FieldOptions(CType)
import qualified Text.DescriptorProtos.FieldOptions.CType as D.FieldOptions.CType(CType(..))
import qualified Text.DescriptorProtos.FileOptions as D(FileOptions)
import qualified Text.DescriptorProtos.FileDescriptorProto as D(FileDescriptorProto)
import qualified Text.DescriptorProtos.FileDescriptorProto as D.FileDescriptorProto(FileDescriptorProto(..))
import qualified Text.DescriptorProtos.FileOptions as D.FileOptions(FileOptions(..))
import qualified Text.DescriptorProtos.FileOptions.OptimizeMode as D.FileOptions(OptimizeMode)
import qualified Text.DescriptorProtos.FileOptions.OptimizeMode as D.FileOptions.OptimizeMode(OptimizeMode(..))
import qualified Text.DescriptorProtos.MethodDescriptorProto as D(MethodDescriptorProto)
import qualified Text.DescriptorProtos.MethodDescriptorProto as D.MethodDescriptorProto(MethodDescriptorProto(..))
import qualified Text.DescriptorProtos.MessageOptions as D(MessageOptions)
import qualified Text.DescriptorProtos.MessageOptions as D.MessageOptions(MessageOptions(..))
import qualified Text.DescriptorProtos.MethodOptions as D(MethodOptions)
import qualified Text.DescriptorProtos.MethodOptions as D.MethodOptions(MethodOptions(..))
import qualified Text.DescriptorProtos.ServiceDescriptorProto as D(ServiceDescriptorProto)
import qualified Text.DescriptorProtos.ServiceDescriptorProto as D.ServiceDescriptorProto(ServiceDescriptorProto(..))
import qualified Text.DescriptorProtos.ServiceOptions as D(ServiceOptions)
import qualified Text.DescriptorProtos.ServiceOptions as D.ServiceOptions(ServiceOptions(..))
import Text.ProtocolBuffers.Header
import Text.ProtocolBuffers.Instances
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Language(emptyDef,LanguageDef(..))
import qualified Text.ParserCombinators.Parsec.Token as T (makeTokenParser,TokenParser(..))
import Data.List(intersperse)
import Data.Maybe(maybe)
import Data.Maybe(maybe)
import Data.Monoid
import System.IO(readFile)
import Data.ByteString.Char8(pack)
import qualified Data.Sequence as Seq
--t1 = mappend (Just (D.FileOptions.OptimizeMode.SPEED)) (Just (D.FileOptions.OptimizeMode.CODE_SIZE))
p = T.makeTokenParser protoDef
-- see google's io/tokenizer.{h,c}
protoDef = emptyDef {
commentStart = []
, commentEnd = []
-- commentLine is really useful
, commentLine = "//"
, nestedComments = False
-- This ident* agrees with comments in io/tokenizer.h
, identStart = letter
, identLetter = alphaNum <|> char '_'
, opStart = pzero
, opLetter = pzero
-- The following reservedNames and reservedOpNames maybe ought to be [] and []
, reservedNames = ["option","message"
,"required","options","repeated"
,"double","float"
,"int32","int64"
,"uint32","uint64"
,"sint32","sint64"
,"fixed32","fixed64"
,"sfixed32","sfixed64"
,"bool","string","bytes"
]
, reservedOpNames = ["="]
, caseSensitive = True
}
skip = T.whiteSpace p
sym = T.symbol p
semi = T.semi p
ident = T.identifier p <?> "Expecting identifier (letter then letter or number or _)"
type US = ()
-- Parsing a proto file produces a stream of items
data TopLevel = P !ByteString
| D !ByteString
| M D.DescriptorProto
| E D.EnumDescriptorProto
| S D.ServiceDescriptorProto
| F D.FieldDescriptorProto
| O D.FileOptions
deriving Show
unique :: String -> [b] -> (Maybe b,Maybe String)
unique e [] = (Nothing,Nothing)
unique e [b] = (Just b,Nothing)
unique e bs = (Just (last bs),Just e)
fileDescriptorProto :: CharParser US D.FileDescriptorProto
fileDescriptorProto = do
skipTop ()
val <- many (topLevel >>= skipTop)
-- -- Alternative to enforce having only a single package directive
-- let (opPackage,mayErr) = unique "Multiple package definitions found, only one expected."
-- [ x | P x <- val ]
-- maybe (return ()) fail mayErr
return (defaultValue { -- D.FileDescriptorProto.package = opPackage
D.FileDescriptorProto.package = mergeConcat [ Just x | P x <- val ]
, D.FileDescriptorProto.dependency = Seq.fromList [ x | D x <- val ]
, D.FileDescriptorProto.message_type = Seq.fromList [ x | M x <- val ]
, D.FileDescriptorProto.enum_type = Seq.fromList [ x | E x <- val ]
, D.FileDescriptorProto.service = Seq.fromList [ x | S x <- val ]
, D.FileDescriptorProto.extension = Seq.fromList [ x | F x <- val ]
, D.FileDescriptorProto.options = mergeConcat [ Just x | O x <- val ]
} )
skipTop :: a -> CharParser US a
skipTop x = skip >> skipMany (semi >> skip) >> return x -- ignore empty declarations
topLevel :: CharParser US TopLevel
topLevel = choice [ messageTop
, enumTop
, serviceTop
, extendTop
, importTop
, packageTop
, optionTop
] <?> "Expected top-level statement (e.g. \"message\")."
messageTop = string "message" >> do
skip
name <- ident
return (M (defaultValue { D.DescriptorProto.name = Just (pack name) }))
enumTop = pzero
serviceTop = pzero
extendTop = pzero
importTop = pzero
packageTop = string "package" >> do
skip
parts <- sepBy1 ident (char '.') <?> "Expect package name (a dotted itentifier, e.g. beep.giggle.whee)"
skip >> semi
return (P (pack (foldr1 (\a b -> a ++ '.':b) parts)))
-- Hard code optionTop avoid only use of reflection in google's parser
-- This can be make reflective after bootstrapping if this program is ever finished
optionTop = string "option" >> do
skip
optionName <- ident
skip
char '='
skip
opt <- optionByName optionName
skip >> semi
return (O opt)
where
optionByName "java_package" = do
bs <- parseString
return (defaultValue { D.FileOptions.java_package = Just bs })
optionByName "java_outer_classname" = do
bs <- parseString
return (defaultValue { D.FileOptions.java_outer_classname = Just bs })
optionByName "java_multiple_files" = do
bool <- parseBool
return (defaultValue { D.FileOptions.java_multiple_files = Just bool })
optionByName "optimize_for" = do
mode <- parseOptimizeMode
return (defaultValue { D.FileOptions.optimize_for = Just mode })
parseString = pzero
parseBool = pzero
parseOptimizeMode = pzero
test f = do
proto <- readFile f
print $ parse fileDescriptorProto f proto
testTmp = fmap (parse fileDescriptorProto f) (readFile f)
where f = "/tmp/test"