proto-lens-protoc-0.8.0.0: app/Data/ProtoLens/Compiler/Generate/Field.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
-- | This module generates the code for decoding and encoding
-- individual field types.
--
-- Upstream docs:
-- <https://developers.google.com/protocol-buffers/docs/encoding#structure>
module Data.ProtoLens.Compiler.Generate.Field
( hsFieldType
, hsFieldVectorType
, FieldEncoding(..)
, fieldEncoding
, lengthy
, groupEnd
, isolatedLengthy
) where
import Data.Text (unpack)
import Data.Word (Word8)
import Lens.Family2
import Proto.Google.Protobuf.Descriptor (FieldDescriptorProto'Type(..))
import Data.ProtoLens.Compiler.Definitions
import GHC.SourceGen
hsFieldType :: Env RdrNameStr -> FieldInfo -> HsType'
hsFieldType env f = let
fd = fieldDescriptor f
in var $ 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 -> messageName m
| otherwise -> error $ "expected TYPE_GROUP for type name"
++ unpack (fd ^. #typeName)
FieldDescriptorProto'TYPE_MESSAGE
| Message m <- definedFieldType fd env -> 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 -> 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"
hsFieldVectorType :: FieldInfo -> HsType'
hsFieldVectorType f = case fieldDescriptor f ^. #type' of
FieldDescriptorProto'TYPE_MESSAGE -> boxed
-- TODO: store enums in unboxed fields.
FieldDescriptorProto'TYPE_ENUM -> boxed
FieldDescriptorProto'TYPE_GROUP -> boxed
FieldDescriptorProto'TYPE_STRING -> boxed
FieldDescriptorProto'TYPE_BYTES -> boxed
_ -> unboxed
where
boxed = var "Data.Vector.Vector"
unboxed = var "Data.Vector.Unboxed.Vector"
-- | A representation for how to encode and decode a particular field type.
data FieldEncoding = FieldEncoding
{ buildFieldType :: HsExpr' -- ^ :: a -> Builder
, parseFieldType :: HsExpr' -- ^ :: Parser a
, wireType :: Word8
}
-- | A variable-length integer, decoded as an unsigned Word64.
varint :: FieldEncoding
varint = FieldEncoding
{ wireType = 0
, buildFieldType = putVarInt'
, parseFieldType = getVarInt'
}
-- | A fixed-length integer (Word64).
fixed64 :: FieldEncoding
fixed64 = FieldEncoding
{ wireType = 1
, buildFieldType = var "Data.ProtoLens.Encoding.Bytes.putFixed64"
, parseFieldType = var "Data.ProtoLens.Encoding.Bytes.getFixed64"
}
-- | A fixed-length integer (Word32).
fixed32 :: FieldEncoding
fixed32 = FieldEncoding
{ wireType = 5
, buildFieldType = var "Data.ProtoLens.Encoding.Bytes.putFixed32"
, parseFieldType = var "Data.ProtoLens.Encoding.Bytes.getFixed32"
}
-- | A ByteString, prefixed by its length (which is encoded as a varint).
lengthy :: FieldEncoding
lengthy = FieldEncoding
{ wireType = 2
, buildFieldType = buildLengthy
, parseFieldType = parseLengthy
}
where
bs = bvar "bs"
len = bvar "len"
buildLengthy =
-- Bind x since it may be a nontrivial expression:
lambda [bs]
$ var "Data.Monoid.<>"
@@ (putVarInt'
@@ (fromIntegral'
@@ (var "Data.ByteString.length" @@ bs)))
@@ (var "Data.ProtoLens.Encoding.Bytes.putBytes" @@ bs)
parseLengthy = do'
[ len <-- getVarInt'
, stmt $ var "Data.ProtoLens.Encoding.Bytes.getBytes"
@@ (fromIntegral' @@ len)
]
group :: FieldEncoding
group = FieldEncoding
{ wireType = 3
, buildFieldType = var "Data.ProtoLens.buildMessage"
, parseFieldType = var "Data.ProtoLens.parseMessage"
}
groupEnd :: FieldEncoding
groupEnd = FieldEncoding
{ wireType = 4
, buildFieldType = var "Prelude.const" @@ var "Data.Monoid.mempty"
, parseFieldType = var "Prelude.return" @@ unit
}
-- Wrap a field encoding with Haskell functions that should always succeed.
bijectField :: HsExpr' -> HsExpr' -> FieldEncoding -> FieldEncoding
bijectField buildF parseF f = FieldEncoding
{ buildFieldType = var "Prelude.." @@ buildFieldType f @@ buildF
, parseFieldType = var "Prelude.fmap" @@ parseF @@ parseFieldType f
, wireType = wireType f
}
-- | Convert a field of one integral type to another.
integralField :: FieldEncoding -> FieldEncoding
integralField = bijectField fromIntegral' fromIntegral'
fieldEncoding :: FieldDescriptorProto'Type -> FieldEncoding
fieldEncoding = \case
FieldDescriptorProto'TYPE_INT64 -> integralField varint
FieldDescriptorProto'TYPE_UINT64 -> varint
FieldDescriptorProto'TYPE_INT32 -> integralField varint
FieldDescriptorProto'TYPE_UINT32 -> integralField varint
FieldDescriptorProto'TYPE_FIXED64 -> fixed64
FieldDescriptorProto'TYPE_FIXED32 -> fixed32
FieldDescriptorProto'TYPE_SFIXED64 -> integralField fixed64
FieldDescriptorProto'TYPE_SFIXED32 -> integralField fixed32
FieldDescriptorProto'TYPE_DOUBLE ->
bijectField
(var "Data.ProtoLens.Encoding.Bytes.doubleToWord")
(var "Data.ProtoLens.Encoding.Bytes.wordToDouble")
fixed64
FieldDescriptorProto'TYPE_FLOAT ->
bijectField
(var "Data.ProtoLens.Encoding.Bytes.floatToWord")
(var "Data.ProtoLens.Encoding.Bytes.wordToFloat")
fixed32
FieldDescriptorProto'TYPE_BOOL ->
bijectField
(lambda [bvar "b"] $ if' (var "b") (int 1) (int 0))
(var "Prelude./=" @@ int 0)
varint
FieldDescriptorProto'TYPE_ENUM ->
-- TODO: don't throw an exception on unknown proto2 enums.
bijectField (var "Prelude.fromEnum") (var "Prelude.toEnum")
$ integralField varint
FieldDescriptorProto'TYPE_SINT64 ->
bijectField
(var "Data.ProtoLens.Encoding.Bytes.signedInt64ToWord")
(var "Data.ProtoLens.Encoding.Bytes.wordToSignedInt64")
$ integralField varint
FieldDescriptorProto'TYPE_SINT32 ->
bijectField
(var "Data.ProtoLens.Encoding.Bytes.signedInt32ToWord")
(var "Data.ProtoLens.Encoding.Bytes.wordToSignedInt32")
$ integralField varint
FieldDescriptorProto'TYPE_BYTES -> lengthy
FieldDescriptorProto'TYPE_STRING -> stringField
FieldDescriptorProto'TYPE_MESSAGE -> message
FieldDescriptorProto'TYPE_GROUP -> group
-- | A string, represented as Data.Text.Text.
stringField :: FieldEncoding
stringField =
FieldEncoding
{ wireType = 2
, buildFieldType = buildString
, parseFieldType = parseString
}
where
len = bvar "len"
buildString = var "Prelude.." @@ buildFieldType lengthy
@@ var "Data.Text.Encoding.encodeUtf8"
parseString = do'
[ len <-- getVarInt'
, stmt $ var "Data.ProtoLens.Encoding.Bytes.getText"
@@ (fromIntegral' @@ len)
]
-- | A protobuf message type.
message :: FieldEncoding
message = lengthy
{ buildFieldType = var "Prelude.." @@
buildFieldType lengthy @@
var "Data.ProtoLens.encodeMessage"
, parseFieldType = isolatedLengthy (var "Data.ProtoLens.parseMessage")
}
-- | Takes a @Parser a@, reads a varint and then runs the parser
-- isolated to the given length.
isolatedLengthy :: HsExpr' -> HsExpr'
isolatedLengthy parser = do'
[ len <-- getVarInt'
, stmt $ var "Data.ProtoLens.Encoding.Bytes.isolate"
@@ (fromIntegral' @@ len)
@@ parser
]
where
len = bvar "len"
-- | Some functions that are used in multiple places in the generated code.
getVarInt', putVarInt', fromIntegral' :: HsExpr'
getVarInt' = var "Data.ProtoLens.Encoding.Bytes.getVarInt"
putVarInt' = var "Data.ProtoLens.Encoding.Bytes.putVarInt"
fromIntegral' = var "Prelude.fromIntegral"