morley-1.19.2: src/Morley/Micheline/Binary.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA
-- SPDX-License-Identifier: LicenseRef-MIT-obsidian-systems
-- | Module that define encoding and decoding function from Expression type
-- to binary format.
module Morley.Micheline.Binary
( decodeExpression
, eitherDecodeExpression
, encodeExpression
, encodeExpression'
) where
import Data.Binary.Builder qualified as Bi
import Data.Binary.Get qualified as Bi
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Unsafe qualified (fromIntegral)
import Morley.Micheline.Binary.Internal
import Morley.Micheline.Expression
import Morley.Util.Binary (UnpackError(..), ensureEnd, launchGet)
-------------------------------------------------
-- Encode
-------------------------------------------------
-- | Encode 'Expression' to 'ByteString'.
encodeExpression :: Expression -> LByteString
encodeExpression = Bi.toLazyByteString . buildExpr
-- | Same as 'encodeExpression', for strict bytestring.
encodeExpression' :: Expression -> BS.ByteString
encodeExpression' = LBS.toStrict . encodeExpression
buildExpr :: Expression -> Bi.Builder
buildExpr = \case
ExpSeq () xs -> buildWord8 2 <> buildDynamic buildList (DynamicSize xs)
ExpPrim () (MichelinePrimAp prim args annots) -> case (args, annots) of
([], []) -> buildWord8 3 <> buildPrim prim
([], _) -> buildWord8 4 <> buildPrim prim <> buildAnnotationList annots
([arg1], []) -> buildWord8 5 <> buildPrim prim <> buildExpr arg1
([arg1], _) -> buildWord8 6 <> buildPrim prim <> buildExpr arg1 <> buildAnnotationList annots
([arg1, arg2], []) -> buildWord8 7 <> buildPrim prim <> buildExpr arg1 <> buildExpr arg2
([arg1, arg2], _) -> buildWord8 8 <> buildPrim prim <> buildExpr arg1 <> buildExpr arg2 <> buildAnnotationList annots
_ -> buildWord8 9 <> buildPrim prim <> buildDynamic buildList (DynamicSize args) <> buildAnnotationList annots
ExpString () x -> buildWord8 1 <> buildDynamic buildText (DynamicSize x)
ExpInt () x -> buildWord8 0 <> buildInteger x
ExpBytes () x -> buildWord8 10 <> buildDynamic buildByteString (DynamicSize x)
buildList :: [Expression] -> Bi.Builder
buildList = foldMap buildExpr
buildPrim :: MichelinePrimitive -> Bi.Builder
buildPrim = buildWord8 . Unsafe.fromIntegral @Int @Word8 . fromEnum
buildAnnotationList :: [Annotation] -> Bi.Builder
buildAnnotationList listAnn = buildDynamic buildText (DynamicSize $ unwords . fmap annotToText $ listAnn)
-------------------------------------------------
-- Decode
-------------------------------------------------
-- | Decode 'Expression' from 'ByteString'.
eitherDecodeExpression :: BS.ByteString -> Either UnpackError Expression
eitherDecodeExpression x = launchGet (getExpr <* ensureEnd) $ LBS.fromStrict x
-- | Partial version of 'eitherDecodeExpression'.
decodeExpression :: HasCallStack => BS.ByteString -> Expression
decodeExpression = either (error . unUnpackError) id . eitherDecodeExpression
getExpr :: Bi.Get Expression
getExpr = Bi.getWord8 >>= \case
0 -> ExpInt () <$> getInteger
1 -> ExpString () . unDynamicSize <$> (getDynamic getText)
2 -> ExpSeq () . unDynamicSize <$> (getDynamic getList)
3 -> ExpPrim () . (\pn -> MichelinePrimAp pn [] []) <$> getPrim
4 -> ExpPrim () <$> (flip MichelinePrimAp [] <$> getPrim <*> getAnnotationList)
5 -> ExpPrim () <$> (MichelinePrimAp <$> getPrim <*> (one <$> getExpr) <*> pure [])
6 -> ExpPrim () <$> (MichelinePrimAp <$> getPrim <*> (one <$> getExpr) <*> getAnnotationList)
7 -> ExpPrim () <$> ((\n a -> MichelinePrimAp n a []) <$> getPrim <*> replicateM 2 getExpr)
8 -> ExpPrim () <$> (MichelinePrimAp <$> getPrim <*> replicateM 2 getExpr <*> getAnnotationList)
9 -> ExpPrim () <$> (MichelinePrimAp <$> getPrim <*> (unDynamicSize <$> (getDynamic getList)) <*> getAnnotationList)
10 -> ExpBytes () . unDynamicSize <$> (getDynamic getByteString)
_ -> fail "invalid Micheline expression tag"
getList :: Bi.Get [Expression]
getList = many getExpr
getPrim :: Bi.Get MichelinePrimitive
getPrim = maybe (fail "unknown Michelson/Micheline opcode") pure . toEnumSafe . fromIntegral
=<< Bi.getWord8
getAnnotationList :: Bi.Get [Annotation]
getAnnotationList = mapM annotFromText . words . unDynamicSize =<< (getDynamic getText)