module Data.ProtoLens.Compiler.Generate(
generateModule,
fileSyntaxType,
ModifyImports,
reexported,
) where
import Control.Arrow (second)
import qualified Data.Foldable as F
import qualified Data.List as List
import qualified Data.Map as Map
import Data.Maybe (isNothing)
import Data.Ord (comparing)
import qualified Data.Set as Set
import Data.String (fromString)
import Data.Text (unpack)
import qualified Data.Text as T
import Data.Tuple (swap)
import Lens.Family2 ((^.))
import Proto.Google.Protobuf.Descriptor
( EnumValueDescriptorProto
, FieldDescriptorProto
, FieldDescriptorProto'Label(..)
, FieldDescriptorProto'Type(..)
, FileDescriptorProto
, defaultValue
, label
, mapEntry
, maybe'oneofIndex
, maybe'packed
, name
, number
, options
, syntax
, type'
, typeName
)
import Data.ProtoLens.Compiler.Combinators
import Data.ProtoLens.Compiler.Definitions
data SyntaxType = Proto2 | Proto3
deriving Eq
fileSyntaxType :: FileDescriptorProto -> SyntaxType
fileSyntaxType f = case f ^. syntax of
"proto2" -> Proto2
"proto3" -> Proto3
"" -> Proto2
s -> error $ "Unknown syntax type " ++ show s
data UseReexport = UseReexport | UseOriginal
deriving (Eq, Read)
generateModule :: ModuleName
-> [ModuleName]
-> SyntaxType
-> ModifyImports
-> Env Name
-> Env QName
-> Module
generateModule modName imports syntaxType modifyImport definitions importedEnv
= module' modName
[ languagePragma $ map fromString
["ScopedTypeVariables", "DataKinds", "TypeFamilies",
"UndecidableInstances",
"MultiParamTypeClasses", "FlexibleContexts", "FlexibleInstances",
"PatternSynonyms", "MagicHash"]
, optionsGhcPragma "-fno-warn-unused-imports"
]
(map importSimple
[ "Prelude", "Data.Int", "Data.Word"]
++ map (modifyImport . importSimple)
[ "Data.ProtoLens", "Data.ProtoLens.Message.Enum"
, "Lens.Family2", "Lens.Family2.Unchecked", "Data.Default.Class"
, "Data.Text", "Data.Map" , "Data.ByteString"
, "Lens.Labels"
]
++ map importSimple imports)
(concatMap generateDecls (Map.elems definitions)
++ concatMap generateFieldDecls allFieldNames)
where
env = Map.union (unqualifyEnv definitions) importedEnv
generateDecls (Message m) = generateMessageDecls syntaxType env m
generateDecls (Enum e) = generateEnumDecls e
allFieldNames = F.toList $ Set.fromList
[ fieldSymbol i
| Message m <- Map.elems definitions
, f <- messageFields m
, i <- fieldInstances (lensInfo syntaxType env f)
]
importSimple :: ModuleName -> ImportDecl ()
importSimple m = ImportDecl
{ importAnn = ()
, importModule = m
, importQualified = True
, importSrc = False
, importSafe = False
, importPkg = Nothing
, importAs = Nothing
, importSpecs = Nothing
}
type ModifyImports = ImportDecl () -> ImportDecl ()
reexported :: ModifyImports
reexported imp@ImportDecl {importModule = m}
= imp { importAs = Just m, importModule = m' }
where
m' = fromString $ "Data.ProtoLens.Reexport." ++ prettyPrint m
generateMessageDecls :: SyntaxType -> Env QName -> MessageInfo Name -> [Decl]
generateMessageDecls syntaxType env info =
[ dataDecl dataName
[recDecl dataName
[ (recordFieldName f, internalType (lensInfo syntaxType env f))
| f <- fields
]
]
["Prelude.Show", "Prelude.Eq"]
] ++
[ instDecl [equalP "a" t, equalP "b" t, classA "Prelude.Functor" ["f"]]
("Lens.Labels.HasLens" `ihApp`
[sym, "f", dataType, dataType, "a", "b"])
[[match "lensOf" [pWildCard] $ fieldAccessor i]]
| f <- fields
, i <- fieldInstances (lensInfo syntaxType env f)
, let t = fieldTypeInstance i
, let sym = tyPromotedString $ fieldSymbol i
]
++
[ instDecl [] ("Data.Default.Class.Default" `ihApp` [dataType])
[
[ match "def" []
$ recConstr (unQual dataName)
[ fieldUpdate (unQual $ recordFieldName f)
(hsFieldDefault syntaxType env (fieldDescriptor f))
| f <- fields
]
]
]
, instDecl [] ("Data.ProtoLens.Message" `ihApp` [dataType])
[[match "descriptor" [] $ descriptorExpr syntaxType env info]]
]
where
dataType = tyCon $ unQual dataName
MessageInfo { messageName = dataName, messageFields = fields} = info
generateEnumDecls :: EnumInfo Name -> [Decl]
generateEnumDecls info =
[ dataDecl dataName
[conDecl n [] | n <- constructorNames]
["Prelude.Show", "Prelude.Eq"]
, instDecl [] ("Data.Default.Class.Default" `ihApp` [dataType])
[[match "def" [] defaultCon]]
, instDecl [] ("Data.ProtoLens.FieldDefault" `ihApp` [dataType])
[[match "fieldDefault" [] defaultCon]]
, instDecl [] ("Data.ProtoLens.MessageEnum" `ihApp` [dataType])
[
[ match "maybeToEnum" [pLitInt k]
$ "Prelude.Just" @@ con (unQual n)
| (n, k) <- constructorNumbers
]
++
[ match "maybeToEnum" [pWildCard] "Prelude.Nothing"
]
++
[ match "showEnum" [pVar n] $ stringExp $ T.unpack pn
| (n, pn) <- constructorProtoNames
]
++
[ match "readEnum" [stringPat $ T.unpack pn]
$ "Prelude.Just" @@ con (unQual n)
| (n, pn) <- constructorProtoNames
]
++
[ match "readEnum" [pWildCard] "Prelude.Nothing"
]
]
, instDecl [] ("Prelude.Enum" `ihApp` [dataType])
[[match "toEnum" ["k__"]
$ "Prelude.maybe" @@ errorMessageExpr @@ "Prelude.id"
@@ ("Data.ProtoLens.maybeToEnum" @@ "k__")]
, [ match "fromEnum" [pApp (unQual c) []] $ litInt k
| (c, k) <- constructorNumbers
]
, succDecl "succ" maxBoundName succPairs
, succDecl "pred" minBoundName $ map swap succPairs
, alias "enumFrom" "Data.ProtoLens.Message.Enum.messageEnumFrom"
, alias "enumFromTo" "Data.ProtoLens.Message.Enum.messageEnumFromTo"
, alias "enumFromThen" "Data.ProtoLens.Message.Enum.messageEnumFromThen"
, alias "enumFromThenTo"
"Data.ProtoLens.Message.Enum.messageEnumFromThenTo"
]
, instDecl [] ("Prelude.Bounded" `ihApp` [dataType])
[[ match "minBound" [] $ con $ unQual minBoundName
, match "maxBound" [] $ con $ unQual maxBoundName
]]
]
++
concat
[ [ patSynSig aliasName dataType
, patSyn (pVar aliasName) (pVar originalName)
]
| EnumValueInfo
{ enumValueName = aliasName
, enumAliasOf = Just originalName
} <- enumValues info
]
where
dataType = tyCon $ unQual dataName
EnumInfo { enumName = dataName, enumDescriptor = ed } = info
constructors :: [(Name, EnumValueDescriptorProto)]
constructors = List.sortBy (comparing ((^. number) . snd))
[(n, d) | EnumValueInfo
{ enumValueName = n
, enumValueDescriptor = d
, enumAliasOf = Nothing
} <- enumValues info
]
constructorNames = map fst constructors
minBoundName = head constructorNames
maxBoundName = last constructorNames
constructorProtoNames = map (second (^. name)) constructors
constructorNumbers = map (second (fromIntegral . (^. number)))
constructors
succPairs = zip constructorNames $ tail constructorNames
succDecl funName boundName thePairs =
match funName [pApp (unQual boundName) []]
("Prelude.error" @@ stringExp (concat
[ prettyPrint dataName, ".", prettyPrint funName, ": bad argument "
, prettyPrint boundName, ". This value would be out of bounds."
]))
:
[ match funName [pApp (unQual from) []] $ con $ unQual to
| (from, to) <- thePairs
]
alias funName implName = [match funName [] implName]
defaultCon = con $ unQual $ head constructorNames
errorMessageExpr = "Prelude.error"
@@ ("Prelude.++" @@ stringExp errorMessage
@@ ("Prelude.show" @@ "k__"))
errorMessage = "toEnum: unknown value for enum " ++ unpack (ed ^. name)
++ ": "
generateFieldDecls :: String -> [Decl]
generateFieldDecls xStr =
[ typeSig [x]
$ tyForAll ["f", "s", "t", "a", "b"]
[classA "Lens.Labels.HasLens" [xSym, "f", "s", "t", "a", "b"]]
$ "Lens.Family2.LensLike" @@ "f" @@ "s" @@ "t" @@ "a" @@ "b"
, funBind [match x []
$ "Lens.Labels.lensOf"
@@ ("Lens.Labels.proxy#" @::@
("Lens.Labels.Proxy#" @@ xSym))
]
]
where
x = fromString xStr
xSym = tyPromotedString xStr
data LensInfo = LensInfo
{ internalType :: Type
, fieldInstances :: [FieldInstanceInfo]
}
data FieldInstanceInfo = FieldInstanceInfo
{ fieldSymbol :: String
, fieldTypeInstance :: Type
, fieldAccessor :: Exp
}
lensInfo :: SyntaxType -> Env QName -> FieldInfo -> LensInfo
lensInfo syntaxType env f = case fd ^. label of
FieldDescriptorProto'LABEL_REQUIRED -> LensInfo baseType
[FieldInstanceInfo
{ fieldSymbol = baseName
, fieldTypeInstance = baseType
, fieldAccessor = rawAccessor
}]
FieldDescriptorProto'LABEL_OPTIONAL
| isDefaultingOptional syntaxType fd
-> LensInfo baseType
[FieldInstanceInfo
{ fieldSymbol = baseName
, fieldTypeInstance = baseType
, fieldAccessor = rawAccessor
}]
FieldDescriptorProto'LABEL_REPEATED
| Just (k,v) <- getMapFields env fd -> let
mapType = "Data.Map.Map" @@ hsFieldType env (fieldDescriptor k)
@@ hsFieldType env (fieldDescriptor v)
in LensInfo mapType
[FieldInstanceInfo
{ fieldSymbol = baseName
, fieldTypeInstance = mapType
, fieldAccessor = rawAccessor
}]
| otherwise -> LensInfo listType
[FieldInstanceInfo
{ fieldSymbol = baseName
, fieldTypeInstance = listType
, fieldAccessor = rawAccessor
}]
FieldDescriptorProto'LABEL_OPTIONAL -> LensInfo maybeType
[FieldInstanceInfo
{ fieldSymbol = baseName
, fieldTypeInstance = baseType
, fieldAccessor = maybeAccessor
}
, FieldInstanceInfo
{ fieldSymbol = maybeName
, fieldTypeInstance = "Prelude.Maybe" @@ baseType
, fieldAccessor = rawAccessor
}
]
where
baseName = overloadedField f
fd = fieldDescriptor f
baseType = hsFieldType env fd
listType = tyList baseType
maybeType = "Prelude.Maybe" @@ baseType
maybeName = "maybe'" ++ baseName
maybeAccessor = "Prelude.." @@ fromString maybeName
@@ ("Data.ProtoLens.maybeLens"
@@ hsFieldValueDefault env fd)
rawAccessor = rawFieldAccessor $ unQual $ recordFieldName f
getMapFields :: Env QName -> FieldDescriptorProto
-> Maybe (FieldInfo, FieldInfo)
getMapFields env f
| f ^. type' == FieldDescriptorProto'TYPE_MESSAGE
, Message m@MessageInfo { messageDescriptor = d } <- definedFieldType f env
, d ^. options.mapEntry
, [f1, f2] <- messageFields m = Just (f1, f2)
| otherwise = Nothing
hsFieldType :: Env QName -> FieldDescriptorProto -> Type
hsFieldType env fd = case fd ^. type' of
FieldDescriptorProto'TYPE_DOUBLE -> "Prelude.Double"
FieldDescriptorProto'TYPE_FLOAT -> "Prelude.Float"
FieldDescriptorProto'TYPE_INT64 -> "Data.Int.Int64"
FieldDescriptorProto'TYPE_UINT64 -> "Data.Word.Word64"
FieldDescriptorProto'TYPE_INT32 -> "Data.Int.Int32"
FieldDescriptorProto'TYPE_FIXED64 -> "Data.Word.Word64"
FieldDescriptorProto'TYPE_FIXED32 -> "Data.Word.Word32"
FieldDescriptorProto'TYPE_BOOL -> "Prelude.Bool"
FieldDescriptorProto'TYPE_STRING -> "Data.Text.Text"
FieldDescriptorProto'TYPE_GROUP
| Message m <- definedFieldType fd env -> tyCon $ messageName m
| otherwise -> error $ "expected TYPE_GROUP for type name"
++ unpack (fd ^. typeName)
FieldDescriptorProto'TYPE_MESSAGE
| Message m <- definedFieldType fd env -> tyCon $ messageName m
| otherwise -> error $ "expected TYPE_MESSAGE for type name"
++ unpack (fd ^. typeName)
FieldDescriptorProto'TYPE_BYTES -> "Data.ByteString.ByteString"
FieldDescriptorProto'TYPE_UINT32 -> "Data.Word.Word32"
FieldDescriptorProto'TYPE_ENUM
| Enum e <- definedFieldType fd env -> tyCon $ enumName e
| otherwise -> error $ "expected TYPE_ENUM for type name"
++ unpack (fd ^. typeName)
FieldDescriptorProto'TYPE_SFIXED32 -> "Data.Int.Int32"
FieldDescriptorProto'TYPE_SFIXED64 -> "Data.Int.Int64"
FieldDescriptorProto'TYPE_SINT32 -> "Data.Int.Int32"
FieldDescriptorProto'TYPE_SINT64 -> "Data.Int.Int64"
hsFieldDefault :: SyntaxType -> Env QName -> FieldDescriptorProto -> Exp
hsFieldDefault syntaxType env fd
= case fd ^. label of
FieldDescriptorProto'LABEL_OPTIONAL
| isDefaultingOptional syntaxType fd -> hsFieldValueDefault env fd
| otherwise -> "Prelude.Nothing"
FieldDescriptorProto'LABEL_REPEATED
| Just _ <- getMapFields env fd -> "Data.Map.empty"
| otherwise -> list []
FieldDescriptorProto'LABEL_REQUIRED -> hsFieldValueDefault env fd
hsFieldValueDefault :: Env QName -> FieldDescriptorProto -> Exp
hsFieldValueDefault env fd = case fd ^. type' of
FieldDescriptorProto'TYPE_MESSAGE -> "Data.Default.Class.def"
FieldDescriptorProto'TYPE_GROUP -> "Data.Default.Class.def"
FieldDescriptorProto'TYPE_ENUM
| T.null def -> "Data.Default.Class.def"
| Enum e <- definedFieldType fd env
, Just v <- List.lookup def [ (enumValueDescriptor v ^. name, enumValueName v)
| v <- enumValues e
]
-> con v
| otherwise -> errorMessage "enum"
_ | T.null def -> "Data.ProtoLens.fieldDefault"
FieldDescriptorProto'TYPE_BOOL
| def == "true" -> "Prelude.True"
| def == "false" -> "Prelude.False"
| otherwise -> errorMessage "bool"
FieldDescriptorProto'TYPE_STRING
-> "Data.Text.pack" @@ stringExp (T.unpack def)
FieldDescriptorProto'TYPE_BYTES
-> "Data.ByteString.pack"
@@ list ((mkByte . fromEnum) <$> T.unpack def)
where mkByte c
| c > 0 && c < 255 = litInt $ fromIntegral c
| otherwise = errorMessage "bytes"
FieldDescriptorProto'TYPE_FLOAT -> defaultFrac $ T.unpack def
FieldDescriptorProto'TYPE_DOUBLE -> defaultFrac $ T.unpack def
_ -> defaultInt $ T.unpack def
where
def = fd ^. defaultValue
errorMessage fieldType
= error $ "Bad default value " ++ show (T.unpack def)
++ " in default value for " ++ fieldType ++ " field "
++ unpack (fd ^. name)
defaultFrac "nan" = "Prelude./" @@ litFrac 0 @@ litFrac 0
defaultFrac "inf" = "Prelude./" @@ litFrac 1 @@ litFrac 0
defaultFrac "-inf" = "Prelude./" @@ litFrac (negate 1) @@ litFrac 0
defaultFrac s = case reads s of
[(x, "")] -> litFrac $ toRational (x :: Double)
_ -> errorMessage "fractional"
defaultInt s = case reads s of
[(x, "")] -> litInt x
_ -> errorMessage "integral"
rawFieldAccessor :: QName -> Exp
rawFieldAccessor f = "Lens.Family2.Unchecked.lens" @@ getter @@ setter
where
getter = var f
setter = lambda ["x__", "y__"]
$ recUpdate "x__" [fieldUpdate f "y__"]
descriptorExpr :: SyntaxType -> Env QName -> MessageInfo Name -> Exp
descriptorExpr syntaxType env m
= let' (map (fieldDescriptorVarBind $ messageName m) $ messageFields m)
$ "Data.ProtoLens.MessageDescriptor"
@@ ("Data.Map.fromList" @@ list fieldsByTag)
@@ ("Data.Map.fromList" @@ list fieldsByTextFormatName)
where
fieldsByTag =
[tuple
[ t, fieldDescriptorVar f ]
| f <- messageFields m
, let t = "Data.ProtoLens.Tag"
@@ litInt (fromIntegral
$ fieldDescriptor f ^. number)
]
fieldsByTextFormatName =
[tuple
[ t, fieldDescriptorVar f ]
| f <- messageFields m
, let t = stringExp $ T.unpack $ textFormatFieldName env
(fieldDescriptor f)
]
fieldDescriptorVar = fromString . fieldDescriptorName
fieldDescriptorName f
= fromString $ overloadedField f ++ "__field_descriptor"
fieldDescriptorVarBind n f
= funBind
[match (fromString $ fieldDescriptorName f) []
$ fieldDescriptorExpr syntaxType env n f
]
textFormatFieldName :: Env QName -> FieldDescriptorProto -> T.Text
textFormatFieldName env descr = case descr ^. type' of
FieldDescriptorProto'TYPE_GROUP
| Message msg <- definedFieldType descr env
-> messageDescriptor msg ^. name
| otherwise -> error $ "expected TYPE_GROUP for type name"
++ T.unpack (descr ^. typeName)
_ -> descr ^. name
fieldDescriptorExpr :: SyntaxType -> Env QName -> Name -> FieldInfo
-> Exp
fieldDescriptorExpr syntaxType env n f =
("Data.ProtoLens.FieldDescriptor"
@@ stringExp (T.unpack $ textFormatFieldName env fd)
@@ (fieldTypeDescriptorExpr (fd ^. type')
@::@
("Data.ProtoLens.FieldTypeDescriptor"
@@ hsFieldType env fd))
@@ fieldAccessorExpr syntaxType env f)
@::@
("Data.ProtoLens.FieldDescriptor" @@ tyCon (unQual n))
where
fd = fieldDescriptor f
fieldAccessorExpr :: SyntaxType -> Env QName -> FieldInfo -> Exp
fieldAccessorExpr syntaxType env f = accessorCon @@ var (unQual hsFieldName)
where
fd = fieldDescriptor f
accessorCon = case fd ^. label of
FieldDescriptorProto'LABEL_REQUIRED
-> "Data.ProtoLens.PlainField" @@ "Data.ProtoLens.Required"
FieldDescriptorProto'LABEL_OPTIONAL
| isDefaultingOptional syntaxType fd
-> "Data.ProtoLens.PlainField" @@ "Data.ProtoLens.Optional"
| otherwise -> "Data.ProtoLens.OptionalField"
FieldDescriptorProto'LABEL_REPEATED
| Just (k, v) <- getMapFields env fd
-> "Data.ProtoLens.MapField"
@@ fromString (overloadedField k)
@@ fromString (overloadedField v)
| otherwise -> "Data.ProtoLens.RepeatedField"
@@ if isPackedField syntaxType fd
then "Data.ProtoLens.Packed"
else "Data.ProtoLens.Unpacked"
hsFieldName
= fromString $ case fd ^. label of
FieldDescriptorProto'LABEL_OPTIONAL
| not (isDefaultingOptional syntaxType fd)
-> "maybe'" ++ overloadedField f
_ -> overloadedField f
isDefaultingOptional :: SyntaxType -> FieldDescriptorProto -> Bool
isDefaultingOptional syntaxType f
= f ^. label == FieldDescriptorProto'LABEL_OPTIONAL
&& syntaxType == Proto3
&& f ^. type' /= FieldDescriptorProto'TYPE_MESSAGE
&& isNothing (f ^. maybe'oneofIndex)
isPackedField :: SyntaxType -> FieldDescriptorProto -> Bool
isPackedField s f = case f ^. options . maybe'packed of
Just t -> t
Nothing -> s == Proto3
&& f ^. type' `notElem`
[ FieldDescriptorProto'TYPE_MESSAGE
, FieldDescriptorProto'TYPE_GROUP
, FieldDescriptorProto'TYPE_STRING
, FieldDescriptorProto'TYPE_BYTES
]
fieldTypeDescriptorExpr :: FieldDescriptorProto'Type -> Exp
fieldTypeDescriptorExpr =
(\n -> fromString $ "Data.ProtoLens." ++ n ++ "Field") . \t -> case t of
FieldDescriptorProto'TYPE_DOUBLE -> "Double"
FieldDescriptorProto'TYPE_FLOAT -> "Float"
FieldDescriptorProto'TYPE_INT64 -> "Int64"
FieldDescriptorProto'TYPE_UINT64 -> "UInt64"
FieldDescriptorProto'TYPE_INT32 -> "Int32"
FieldDescriptorProto'TYPE_FIXED64 -> "Fixed64"
FieldDescriptorProto'TYPE_FIXED32 -> "Fixed32"
FieldDescriptorProto'TYPE_BOOL -> "Bool"
FieldDescriptorProto'TYPE_STRING -> "String"
FieldDescriptorProto'TYPE_GROUP -> "Group"
FieldDescriptorProto'TYPE_MESSAGE -> "Message"
FieldDescriptorProto'TYPE_BYTES -> "Bytes"
FieldDescriptorProto'TYPE_UINT32 -> "UInt32"
FieldDescriptorProto'TYPE_ENUM -> "Enum"
FieldDescriptorProto'TYPE_SFIXED32 -> "SFixed32"
FieldDescriptorProto'TYPE_SFIXED64 -> "SFixed64"
FieldDescriptorProto'TYPE_SINT32 -> "SInt32"
FieldDescriptorProto'TYPE_SINT64 -> "SInt64"