{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeOperators #-} -- | -- Module : Pinch.Internal.Value -- Copyright : (c) Abhinav Gupta 2015 -- License : BSD3 -- -- Maintainer : Abhinav Gupta -- Stability : experimental -- -- This module defines an intermediate representation for Thrift values and -- functions to work with the intermediate representation. module Pinch.Internal.Value ( Value(..) , SomeValue(..) , castValue , valueTType ) where import Control.DeepSeq (NFData (..)) import Data.ByteString (ByteString) import Data.Hashable (Hashable (..)) import Data.HashMap.Strict (HashMap) import Data.HashSet (HashSet) import Data.Int (Int16, Int32, Int64, Int8) import Data.Typeable ((:~:) (..), Typeable, cast, eqT) import Data.Vector (Vector) import qualified Data.HashMap.Strict as M import qualified Data.HashSet as S import qualified Data.Vector as V import Pinch.Internal.TType -- | @Value@ maps directly to serialized representation of Thrift types. It -- contains about as much information as what gets sent over the wire. -- @Value@ objects are tagged with different 'TType' values to indicate the -- type of the value. -- -- Typical usage will not involve accessing the constructors for this type. -- 'Pinch.Pinchable.Pinchable' must be used to construct 'Value' objects or -- convert them back to original types. data Value a where VBool :: !Bool -> Value TBool VByte :: !Int8 -> Value TByte VDouble :: !Double -> Value TDouble VInt16 :: !Int16 -> Value TInt16 VInt32 :: !Int32 -> Value TInt32 VInt64 :: !Int64 -> Value TInt64 VBinary :: !ByteString -> Value TBinary VStruct :: !(HashMap Int16 SomeValue) -> Value TStruct VMap :: forall k v. (IsTType k, IsTType v) => !(HashMap (Value k) (Value v)) -> Value TMap VSet :: forall a. IsTType a => !(HashSet (Value a)) -> Value TSet VList :: forall a. IsTType a => !(Vector (Value a)) -> Value TList deriving Typeable deriving instance Show (Value a) instance Eq (Value a) where VBool a == VBool b = a == b VByte a == VByte b = a == b VDouble a == VDouble b = a == b VInt16 a == VInt16 b = a == b VInt32 a == VInt32 b = a == b VInt64 a == VInt64 b = a == b VBinary a == VBinary b = a == b VStruct a == VStruct b = a == b VMap as == VMap bs = areEqual as bs VSet as == VSet bs = areEqual as bs VList as == VList bs = areEqual as bs _ == _ = False instance NFData (Value a) where rnf (VBool a) = rnf a rnf (VByte a) = rnf a rnf (VDouble a) = rnf a rnf (VInt16 a) = rnf a rnf (VInt32 a) = rnf a rnf (VInt64 a) = rnf a rnf (VBinary a) = rnf a rnf (VStruct a) = rnf a rnf (VMap as) = rnf as rnf (VSet as) = rnf as rnf (VList as) = rnf as -- | 'SomeValue' holds any value, regardless of type. This may be used when -- the type of the value is not necessarily known at compile time. Typically, -- this will be pattern matched on and code that depends on the value's -- 'TType' will go inside the scope of the match. data SomeValue where SomeValue :: (IsTType a) => !(Value a) -> SomeValue deriving Typeable deriving instance Show SomeValue instance Eq SomeValue where SomeValue a == SomeValue b = areEqual a b instance NFData SomeValue where rnf (SomeValue a) = rnf a -- | Safely attempt to cast 'SomeValue' into a 'Value'. castValue :: Typeable a => SomeValue -> Maybe (Value a) castValue (SomeValue v) = cast v -- | Get the 'TType' of a 'Value'. valueTType :: IsTType a => Value a -> TType a valueTType _ = ttype -- | Helper to compare two types that are not known to be equal at compile -- time. areEqual :: forall a b. (Typeable a, Typeable b, Eq a) => a -> b -> Bool areEqual x y = case eqT of Nothing -> False Just (Refl :: a :~: b) -> x == y instance Hashable (Value a) where hashWithSalt s a = case a of VBinary x -> s `hashWithSalt` (0 :: Int) `hashWithSalt` x VBool x -> s `hashWithSalt` (1 :: Int) `hashWithSalt` x VByte x -> s `hashWithSalt` (2 :: Int) `hashWithSalt` x VDouble x -> s `hashWithSalt` (3 :: Int) `hashWithSalt` x VInt16 x -> s `hashWithSalt` (4 :: Int) `hashWithSalt` x VInt32 x -> s `hashWithSalt` (5 :: Int) `hashWithSalt` x VInt64 x -> s `hashWithSalt` (6 :: Int) `hashWithSalt` x VList xs -> V.foldr' (flip hashWithSalt) (s `hashWithSalt` (7 :: Int)) xs VMap xs -> M.foldrWithKey (\k v s' -> s' `hashWithSalt` k `hashWithSalt` v) (s `hashWithSalt` (8 :: Int)) xs VSet xs -> S.foldr (flip hashWithSalt) (s `hashWithSalt` (9 :: Int)) xs VStruct fields -> M.foldrWithKey (\k v s' -> s' `hashWithSalt` k `hashWithSalt` v) (s `hashWithSalt` (10 :: Int)) fields instance Hashable SomeValue where hashWithSalt s (SomeValue v) = hashWithSalt s v