protocol-buffers-0.0.2: src/ProtocolBuffers/ParseProto.hs
module ProtocolBuffers.ParseProto where
-- see compiler/parser.cc in the google source code
import qualified DescriptorProtos.DescriptorProto as D(DescriptorProto)
import qualified DescriptorProtos.DescriptorProto as D.DescriptorProto(DescriptorProto(..))
import qualified DescriptorProtos.DescriptorProto.ExtensionRange as D.DescriptorProto(ExtensionRange)
import qualified DescriptorProtos.DescriptorProto.ExtensionRange as D.DescriptorProto.ExtensionRange(ExtensionRange(..))
import qualified DescriptorProtos.EnumDescriptorProto as D(EnumDescriptorProto)
import qualified DescriptorProtos.EnumDescriptorProto as D.EnumDescriptorProto(EnumDescriptorProto(..))
import qualified DescriptorProtos.EnumOptions as D(EnumOptions)
import qualified DescriptorProtos.EnumOptions as D.EnumOptions(EnumOptions(..))
import qualified DescriptorProtos.EnumValueDescriptorProto as D(EnumValueDescriptorProto)
import qualified DescriptorProtos.EnumValueDescriptorProto as D.EnumValueDescriptorProto(EnumValueDescriptorProto(..))
import qualified DescriptorProtos.EnumValueOptions as D(EnumValueOptions)
import qualified DescriptorProtos.EnumValueOptions as D.EnumValueOptions(EnumValueOptions(..))
import qualified DescriptorProtos.FieldDescriptorProto as D(FieldDescriptorProto)
import qualified DescriptorProtos.FieldDescriptorProto as D.FieldDescriptorProto(FieldDescriptorProto(..))
import qualified DescriptorProtos.FieldDescriptorProto.Label as D.FieldDescriptorProto(Label)
import qualified DescriptorProtos.FieldDescriptorProto.Label as D.FieldDescriptorProto.Label(Label(..))
import qualified DescriptorProtos.FieldDescriptorProto.Type as D.FieldDescriptorProto(Type)
import qualified DescriptorProtos.FieldDescriptorProto.Type as D.FieldDescriptorProto.Type(Type(..))
import qualified DescriptorProtos.FieldOptions as D(FieldOptions)
import qualified DescriptorProtos.FieldOptions as D.FieldOptions(FieldOptions(..))
import qualified DescriptorProtos.FieldOptions.CType as D.FieldOptions(CType)
import qualified DescriptorProtos.FieldOptions.CType as D.FieldOptions.CType(CType(..))
import qualified DescriptorProtos.FileOptions as D(FileOptions)
import qualified DescriptorProtos.FileDescriptorProto as D(FileDescriptorProto)
import qualified DescriptorProtos.FileDescriptorProto as D.FileDescriptorProto(FileDescriptorProto(..))
import qualified DescriptorProtos.FileOptions as D.FileOptions(FileOptions(..))
import qualified DescriptorProtos.FileOptions.OptimizeMode as D.FileOptions(OptimizeMode)
import qualified DescriptorProtos.FileOptions.OptimizeMode as D.FileOptions.OptimizeMode(OptimizeMode(..))
import qualified DescriptorProtos.MethodDescriptorProto as D(MethodDescriptorProto)
import qualified DescriptorProtos.MethodDescriptorProto as D.MethodDescriptorProto(MethodDescriptorProto(..))
import qualified DescriptorProtos.MessageOptions as D(MessageOptions)
import qualified DescriptorProtos.MessageOptions as D.MessageOptions(MessageOptions(..))
import qualified DescriptorProtos.MethodOptions as D(MethodOptions)
import qualified DescriptorProtos.MethodOptions as D.MethodOptions(MethodOptions(..))
import qualified DescriptorProtos.ServiceDescriptorProto as D(ServiceDescriptorProto)
import qualified DescriptorProtos.ServiceDescriptorProto as D.ServiceDescriptorProto(ServiceDescriptorProto(..))
import qualified DescriptorProtos.ServiceOptions as D(ServiceOptions)
import qualified DescriptorProtos.ServiceOptions as D.ServiceOptions(ServiceOptions(..))
import ProtocolBuffers.Header
import ProtocolBuffers.Instances
import ProtocolBuffers.Option
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 :: OptionFlag a => String -> [b] -> (Option a b,Maybe String)
unique e [] = (Absent,Nothing)
unique e [b] = (Present b,Nothing)
unique e bs = (Present (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 = mconcat [ Present 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 = mconcat [ Present 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 = Present (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 = Present bs })
optionByName "java_outer_classname" = do
bs <- parseString
return (defaultValue { D.FileOptions.java_outer_classname = Present bs })
optionByName "java_multiple_files" = do
bool <- parseBool
return (defaultValue { D.FileOptions.java_multiple_files = Present bool })
optionByName "optimize_for" = do
mode <- parseOptimizeMode
return (defaultValue { D.FileOptions.optimize_for = Present 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"