packages feed

inferno-types-0.1.0.0: src/Inferno/Types/VersionControl.hs

{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
-- Needed for a ToADTArbitrary on VCObjectHash
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Inferno.Types.VersionControl where

import Control.DeepSeq (NFData)
import Crypto.Hash (Context, Digest, digestFromByteString, hash, hashFinalize, hashInit, hashUpdate)
import Crypto.Hash.Algorithms (SHA256)
import Data.Aeson (FromJSON (..), FromJSONKey, ToJSON (..), ToJSONKey, withText)
import Data.ByteArray (ByteArrayAccess, convert)
import Data.ByteArray.Pack (fill, putStorable)
import Data.ByteString (ByteString, pack)
import qualified Data.ByteString.Base64.URL as Base64
import qualified Data.ByteString.Char8 as Char8
import Data.Data (Data)
import Data.Hashable (Hashable (hashWithSalt))
import Data.Int (Int32, Int64)
import qualified Data.IntMap as IntMap
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Text (Text, unpack)
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import Data.Word (Word32, Word64)
import Foreign.C.Types (CTime (..))
import Foreign.Storable (sizeOf)
import GHC.Generics (C1, Constructor, D1, Generic, K1 (..), M1 (..), Rec1 (..), Rep, S1, U1, conName, from, (:*:) (..), (:+:) (..))
import Inferno.Types.Syntax
  ( Comment (..),
    Expr (..),
    ExtIdent (..),
    Fixity,
    IStr (..),
    Ident (..),
    ImplExpl (..),
    Import (..),
    InfixFixity,
    Lit (..),
    ModuleName (..),
    Pat (..),
    Scoped (..),
    SomeIStr (..),
    TList,
    tListToList,
  )
import Inferno.Types.Type
  ( BaseType (..),
    ImplType (..),
    InfernoType (..),
    Namespace,
    TCScheme (..),
    TV (..),
    TypeClass (..),
    TypeMetadata (..),
  )
import Servant.API (FromHttpApiData (..), ToHttpApiData (..))
import Test.QuickCheck.Arbitrary (Arbitrary (..))
import Test.QuickCheck.Arbitrary.ADT
import Text.Megaparsec (SourcePos)

newtype VCObjectHash = VCObjectHash {vcObjectHashDigest :: Digest SHA256}
  deriving stock (Generic, Data)
  deriving anyclass (ToJSONKey, FromJSONKey, NFData, ToADTArbitrary)
  deriving newtype (Eq, Ord, Read, ByteArrayAccess)

-- Orphan Instance Required for ToADTArbitrary VCObjectHash
instance Arbitrary (Digest SHA256) where
  arbitrary = hash . pack <$> arbitrary

instance Show VCObjectHash where
  show = Char8.unpack . vcObjectHashToByteString

instance ToJSON VCObjectHash where
  toJSON = toJSON . decodeUtf8 . vcObjectHashToByteString

instance FromJSON VCObjectHash where
  parseJSON = withText "VCObjectHash" $ \piece -> do
    b64 <- either fail pure $ Base64.decode $ encodeUtf8 piece
    maybe (fail $ unpack $ "Cannot decode hash " <> piece) (pure . VCObjectHash) . digestFromByteString $ b64

instance Arbitrary VCObjectHash where
  arbitrary = VCObjectHash . hash . Char8.pack <$> arbitrary

instance Hashable VCObjectHash where
  hashWithSalt salt (VCObjectHash digest) =
    hashWithSalt salt $ convert @(Digest SHA256) @ByteString digest

-- | Typeclass of hashable objects
class VCHashUpdate obj where
  (&<) :: Context SHA256 -> obj -> Context SHA256
  default (&<) :: (Generic obj, GenericVCHashUpdate (Rep obj)) => Context SHA256 -> obj -> Context SHA256
  ctxt &< o = genHashUpdate ctxt $ from o

hashUpdateVia :: ByteArrayAccess ba => (obj -> ba) -> Context SHA256 -> obj -> Context SHA256
hashUpdateVia toBAA ctxt obj = ctxt `hashUpdate` (toBAA obj)
{-# INLINE hashUpdateVia #-}

newtype VCHashUpdateViaShow a = VCHashUpdateViaShow {unVCHashUpdateViaShow :: a}

instance Show a => VCHashUpdate (VCHashUpdateViaShow a) where
  (&<) = hashUpdateVia $ Char8.pack . show . unVCHashUpdateViaShow

deriving via (VCHashUpdateViaShow ()) instance VCHashUpdate ()

deriving via (VCHashUpdateViaShow Int) instance VCHashUpdate Int

deriving via (VCHashUpdateViaShow InfixFixity) instance VCHashUpdate InfixFixity

deriving via (VCHashUpdateViaShow Fixity) instance VCHashUpdate Fixity

instance VCHashUpdate CTime where
  ctxt &< (CTime t) = ctxt &< ("CTime" :: ByteString) &< t

instance VCHashUpdate a => VCHashUpdate (Maybe a) where
  ctxt &< Nothing = ctxt
  ctxt &< (Just o) = ctxt &< o

instance (VCHashUpdate a, VCHashUpdate b) => VCHashUpdate (a, b) where
  ctxt &< (a, b) = ctxt &< a &< b

instance (VCHashUpdate a, VCHashUpdate b, VCHashUpdate c) => VCHashUpdate (a, b, c) where
  ctxt &< (a, b, c) = ctxt &< a &< b &< c

instance (VCHashUpdate a, VCHashUpdate b, VCHashUpdate c, VCHashUpdate d) => VCHashUpdate (a, b, c, d) where
  ctxt &< (a, b, c, d) = ctxt &< a &< b &< c &< d

instance (VCHashUpdate a, VCHashUpdate b, VCHashUpdate c, VCHashUpdate d, VCHashUpdate e) => VCHashUpdate (a, b, c, d, e) where
  ctxt &< (a, b, c, d, e) = ctxt &< a &< b &< c &< d &< e

instance VCHashUpdate ByteString where
  (&<) = hashUpdate

instance VCHashUpdate Text where
  ctxt &< t = ctxt `hashUpdate` encodeUtf8 t

instance VCHashUpdate a => VCHashUpdate [a] where
  ctxt &< [] = ctxt &< ("[]" :: ByteString)
  ctxt &< (o : os) = ctxt &< (":" :: ByteString) &< o &< os

instance VCHashUpdate ExtIdent where
  ctxt &< (ExtIdent (Left a)) = ctxt &< ("var$" :: ByteString) &< a
  ctxt &< (ExtIdent (Right b)) = ctxt &< ("reg$" :: ByteString) &< b

instance VCHashUpdate a => VCHashUpdate (NonEmpty.NonEmpty a) where
  ctxt &< xs = ctxt &< (NonEmpty.toList xs)

instance VCHashUpdate a => VCHashUpdate (Set.Set a) where
  ctxt &< xs = ctxt &< (Set.toList xs)

instance (VCHashUpdate k, VCHashUpdate a) => VCHashUpdate (Map.Map k a) where
  ctxt &< m = ctxt &< (Map.toList m)

instance VCHashUpdate a => VCHashUpdate (IntMap.IntMap a) where
  ctxt &< m = ctxt &< (IntMap.toList m)

class GenericVCHashUpdate f where
  genHashUpdate :: Context SHA256 -> f p -> Context SHA256

instance GenericVCHashUpdate U1 where
  genHashUpdate ctxt _ = ctxt

instance (VCHashUpdate a) => GenericVCHashUpdate (K1 i a) where
  genHashUpdate ctxt (K1 x) = ctxt &< x

instance GenericVCHashUpdate f => GenericVCHashUpdate (D1 c f) where
  genHashUpdate ctxt (M1 x) = genHashUpdate ctxt x

instance (Constructor c, GenericVCHashUpdate f) => GenericVCHashUpdate (C1 c f) where
  genHashUpdate ctxt x@(M1 y) = ctxt &< (Char8.pack $ conName x) `genHashUpdate` y

instance GenericVCHashUpdate f => GenericVCHashUpdate (S1 c f) where
  genHashUpdate ctxt (M1 x) = genHashUpdate ctxt x

instance (GenericVCHashUpdate a, GenericVCHashUpdate b) => GenericVCHashUpdate (a :+: b) where
  genHashUpdate ctxt = \case
    L1 a -> genHashUpdate ctxt a
    R1 b -> genHashUpdate ctxt b

instance (GenericVCHashUpdate a, GenericVCHashUpdate b) => GenericVCHashUpdate (a :*: b) where
  genHashUpdate ctxt (a :*: b) = ctxt `genHashUpdate` a `genHashUpdate` b

instance GenericVCHashUpdate f => GenericVCHashUpdate (Rec1 f) where
  genHashUpdate ctxt (Rec1 a) = genHashUpdate ctxt a

deriving newtype instance VCHashUpdate Ident

deriving newtype instance VCHashUpdate ModuleName

instance VCHashUpdate VCObjectHash where
  ctxt &< (VCObjectHash h) = ctxt `hashUpdate` h

deriving instance VCHashUpdate ImplExpl

instance VCHashUpdate Int64 where
  (&<) =
    hashUpdateVia $
      (\i64 -> either error (id :: ByteString -> ByteString) $ fill (sizeOf i64) $ putStorable i64)

instance VCHashUpdate Int32 where
  (&<) =
    hashUpdateVia $
      (\i32 -> either error (id :: ByteString -> ByteString) $ fill (sizeOf i32) $ putStorable i32)

instance VCHashUpdate Double where
  (&<) =
    hashUpdateVia $
      (\d -> either error (id :: ByteString -> ByteString) $ fill (sizeOf d) $ putStorable d)

instance VCHashUpdate Word32 where
  (&<) =
    hashUpdateVia $
      (\w32 -> either error (id :: ByteString -> ByteString) $ fill (sizeOf w32) $ putStorable w32)

instance VCHashUpdate Word64 where
  (&<) =
    hashUpdateVia $
      (\w64 -> either error (id :: ByteString -> ByteString) $ fill (sizeOf w64) $ putStorable w64)

deriving instance VCHashUpdate Lit

instance VCHashUpdate e => VCHashUpdate (SomeIStr e) where
  ctxt &< (SomeIStr istr) = ctxt &< istr

instance VCHashUpdate e => VCHashUpdate (IStr f e) where
  ctxt &< istr = case istr of
    ISEmpty -> ctxt &< ("ISEmpty" :: ByteString)
    ISStr s is -> ctxt &< ("ISStr" :: ByteString) &< s &< is
    ISExpr e is -> ctxt &< ("ISExpr" :: ByteString) &< e &< is

deriving instance VCHashUpdate a => VCHashUpdate (Comment a)

instance VCHashUpdate a => VCHashUpdate (TList a) where
  ctxt &< ts = ctxt &< (tListToList ts)

deriving instance VCHashUpdate a => VCHashUpdate (Import a)

deriving instance (VCHashUpdate hash, VCHashUpdate a) => VCHashUpdate (Pat hash a)

deriving instance VCHashUpdate a => VCHashUpdate (Scoped a)

deriving instance (VCHashUpdate hash, VCHashUpdate a) => VCHashUpdate (Expr hash a)

deriving instance VCHashUpdate BaseType

deriving newtype instance VCHashUpdate TV

deriving instance VCHashUpdate InfernoType

deriving instance VCHashUpdate TypeClass

deriving instance VCHashUpdate ty => VCHashUpdate (TypeMetadata ty)

deriving instance VCHashUpdate ImplType

deriving instance VCHashUpdate Namespace

deriving instance VCHashUpdate TCScheme

deriving via (VCHashUpdateViaShow SourcePos) instance VCHashUpdate SourcePos

data Pinned a = Local | Builtin VCObjectHash | UnderVC a
  deriving (Show, Eq, Ord, Generic, Functor, Data, ToJSON, FromJSON, VCHashUpdate)

pinnedToMaybe :: Pinned VCObjectHash -> Maybe VCObjectHash
pinnedToMaybe = \case
  Local -> Nothing
  Builtin h -> Just h
  UnderVC h -> Just h

pinnedUnderVCToMaybe :: Pinned a -> Maybe a
pinnedUnderVCToMaybe = \case
  UnderVC h -> Just h
  _ -> Nothing

vcObjectHashToByteString :: VCObjectHash -> ByteString
vcObjectHashToByteString = Base64.encode . convert . vcObjectHashDigest

byteStringToVCObjectHash :: ByteString -> Maybe VCObjectHash
byteStringToVCObjectHash b = do
  b64 <- either (const Nothing) Just $ Base64.decode b
  VCObjectHash <$> digestFromByteString b64

vcHash :: VCHashUpdate obj => obj -> VCObjectHash
vcHash o = VCObjectHash $ hashFinalize $ hashInit &< o

instance FromHttpApiData VCObjectHash where
  parseUrlPiece t = maybe (Left $ "Cannot decode hash " <> t) Right $ byteStringToVCObjectHash $ encodeUtf8 t

instance ToHttpApiData VCObjectHash where
  toUrlPiece = decodeUtf8 . vcObjectHashToByteString