{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeInType #-} {-# LANGUAGE TypeOperators #-} -- | -- Module : Codec.Scale.Generic -- Copyright : Alexander Krupenkin 2016 -- License : BSD3 -- -- Maintainer : mail@akru.me -- Stability : experimental -- Portability : unportable -- -- This module defines generic codec instances for data structures (including tuples) -- and enums (tagged-unions in Rust). -- module Codec.Scale.Generic () where import Data.Serialize.Get (Get, getWord8) import Data.Serialize.Put (PutM, putWord8) import Data.Word (Word8) import Generics.SOP (All, Compose, I (..), NP (..), NS (..), SOP (..), unSOP, unZ) import Codec.Scale.Class (Decode (..), Encode (..), GDecode (..), GEncode (..)) -- Enum has multiple sum types. instance ( GEncode (NP f xs) , GEncode (NP f ys) , All (GEncode `Compose` NP f) xss ) => GEncode (SOP f (xs ': ys ': xss)) where gPut = go 0 . unSOP where go :: forall f as . All (GEncode `Compose` f) as => Word8 -> NS f as -> PutM () go !acc (Z x) = putWord8 acc >> gPut x go !acc (S x) = go (acc + 1) x -- Structures has only one sum type. instance GEncode (NP f xs) => GEncode (SOP f '[xs]) where gPut = gPut . unZ . unSOP -- Product serialization is just encode each field step by step. instance (Encode a, GEncode (NP I as)) => GEncode (NP I (a ': as)) where gPut (I a :* as) = put a >> gPut as -- Finish when all fields handled. instance GEncode (NP I '[]) where gPut _ = mempty -- | Enum parser definition. -- -- The index of sum type to parse given as an argument. class EnumParser xs where enumParser :: All (GDecode `Compose` NP f) xs => Word8 -> Get (NS (NP f) xs) -- Enumerate enum index, zero means that we reach the goal. instance EnumParser as => EnumParser (a ': as) where enumParser !i | i > 0 = S <$> enumParser (i - 1) | otherwise = Z <$> gGet -- When index out of type scope raise the error. instance EnumParser '[] where enumParser _ = fail "wrong prefix during enum decoding" -- Decode enum when multiple sum types. instance ( GDecode (NP f xs) , GDecode (NP f ys) , All (GDecode `Compose` NP f) xss , EnumParser xss ) => GDecode (SOP f (xs ': ys ': xss)) where gGet = SOP <$> (enumParser =<< getWord8) -- Decode plain structure when only one sum type. instance GDecode (NP f as) => GDecode (SOP f '[as]) where gGet = SOP . Z <$> gGet -- Decode each field in sequence. instance (Decode a, GDecode (NP I as)) => GDecode (NP I (a ': as)) where gGet = (:*) <$> (I <$> get) <*> gGet -- Finish decoding when empty. instance GDecode (NP I '[]) where gGet = return Nil