{-# language AllowAmbiguousTypes , DataKinds , ExplicitNamespaces , FlexibleInstances , GADTs , KindSignatures , PolyKinds , ScopedTypeVariables , TypeApplications , TypeFamilies , TypeOperators , TypeSynonymInstances , UndecidableInstances #-} module Shwifty.Codec ( Codec(..) , ModifyOptions(..) , AsIs , type (&) , Label(..) , Drop , DontGenerate , Implement , RawValue , CanBeRawValue , TypeAlias , NewtypeTag , DontLowercase , OmitField , OmitCase , MakeBase ) where import Data.Kind (Constraint) import Data.Proxy (Proxy(..)) import GHC.TypeLits ( KnownSymbol, Symbol, symbolVal , TypeError, ErrorMessage(..) ) import Shwifty.Class import Shwifty.Types -- | Modify options. class ModifyOptions tag where modifyOptions :: Options -> Options -- | No modifications type AsIs = () instance ModifyOptions AsIs where modifyOptions = id -- | A carrier for modifiers. data Codec tag = Codec instance ModifyOptions tag => ModifyOptions (Codec tag) where modifyOptions = modifyOptions @tag infixr 6 & -- | Combine modifications. data a & b instance forall a b. (ModifyOptions a, ModifyOptions b) => ModifyOptions (a & b) where modifyOptions = modifyOptions @a . modifyOptions @b -- | Label modifiers. data Label = TyCon -- ^ Type constructor modifier | DataCon -- ^ Data constructor modifiers | Field -- ^ Field label modifiers -- | Modify a label by dropping a string data Drop (label :: Label) (string :: Symbol) instance KnownSymbol string => ModifyOptions (Drop 'TyCon string) where modifyOptions options = options { typeConstructorModifier = drop (length (symbolVal (Proxy @string))) } instance KnownSymbol string => ModifyOptions (Drop 'DataCon string) where modifyOptions options = options { constructorModifier = drop (length (symbolVal (Proxy @string))) } instance KnownSymbol string => ModifyOptions (Drop 'Field string) where modifyOptions options = options { fieldLabelModifier = drop (length (symbolVal (Proxy @string))) } -- | Don't generate a specific class. data DontGenerate (cls :: * -> Constraint) class GenerateClass (c :: * -> Constraint) where classModifier :: Options -> Options instance GenerateClass ToSwiftData where classModifier options = options { generateToSwiftData = False } instance GenerateClass ToSwift where classModifier options = options { generateToSwift = False } instance GenerateClass c => ModifyOptions (DontGenerate c) where modifyOptions = classModifier @c -- | Add protocols data Implement (protocol :: Protocol) class KnownProtocol (p :: Protocol) where protocolVal :: Protocol instance KnownProtocol 'Equatable where protocolVal = Equatable instance KnownProtocol 'Hashable where protocolVal = Hashable instance KnownProtocol 'Codable where protocolVal = Codable instance ModifyOptions (Implement 'Equatable) where modifyOptions options = options { dataProtocols = Equatable : dataProtocols options } instance ModifyOptions (Implement 'Hashable) where modifyOptions options = options { dataProtocols = Hashable : dataProtocols options } instance ModifyOptions (Implement 'Codable) where modifyOptions options = options { dataProtocols = Codable : dataProtocols options } -- | Add a rawValue data RawValue (ty :: Ty) -- | A Class that indicates that this swift type -- can be a rawValue. The value of 'getRawValue' -- will be its actual rawValue. class CanBeRawValue (ty :: Ty) where getRawValue :: Ty instance CanBeRawValue 'Str where getRawValue = Str instance CanBeRawValue 'I where getRawValue = I instance CanBeRawValue 'I8 where getRawValue = I8 instance CanBeRawValue 'I16 where getRawValue = I16 instance CanBeRawValue 'I32 where getRawValue = I32 instance CanBeRawValue 'I64 where getRawValue = I64 instance CanBeRawValue 'U where getRawValue = U instance CanBeRawValue 'U8 where getRawValue = U8 instance CanBeRawValue 'U16 where getRawValue = U16 instance CanBeRawValue 'U32 where getRawValue = U32 instance CanBeRawValue 'U64 where getRawValue = U64 instance CanBeRawValue ty => ModifyOptions (RawValue ty) where modifyOptions options = options { dataRawValue = Just (getRawValue @ty) } -- | Make it a type alias (only applies to newtypes) data TypeAlias instance ModifyOptions TypeAlias where modifyOptions options = options { typeAlias = True } -- | Make it a newtype tag (only applies to newtype tags) data NewtypeTag instance ModifyOptions NewtypeTag where modifyOptions options = options { newtypeTag = True } -- | Dont lower-case fields/cases data DontLowercase (someKind :: Label) instance TypeError ('Text "Cannot apply DontLowercase to TyCon") => ModifyOptions (DontLowercase 'TyCon) where modifyOptions _ = error "UNREACHABLE" instance ModifyOptions (DontLowercase 'DataCon) where modifyOptions options = options { lowerFirstCase = False } instance ModifyOptions (DontLowercase 'Field) where modifyOptions options = options { lowerFirstField = False } -- | Omit a field data OmitField (field :: Symbol) instance KnownSymbol field => ModifyOptions (OmitField field) where modifyOptions options = options { omitFields = symbolVal (Proxy @field) : omitFields options } -- | Omit a case data OmitCase (cas :: Symbol) instance KnownSymbol cas => ModifyOptions (OmitCase cas) where modifyOptions options = options { omitCases = symbolVal (Proxy @cas) : omitCases options } -- | Make a base type data MakeBase (rawValue :: Maybe Ty) (protocols :: [Protocol]) instance forall ty protocols. (CanBeRawValue ty, ProtocolList protocols) => ModifyOptions (MakeBase ('Just ty) protocols) where modifyOptions options = options { makeBase = (,,) True (Just (getRawValue @ty)) (protocolList @protocols) } instance forall protocols. (ProtocolList protocols) => ModifyOptions (MakeBase 'Nothing protocols) where modifyOptions options = options { makeBase = (,,) True Nothing (protocolList @protocols) } data SomeProtocol where SomeProtocol :: KnownProtocol p => SomeProtocol class ProtocolList (x :: [Protocol]) where protocolList :: [Protocol] instance ProtocolList '[] where protocolList = [] instance forall p ps. (KnownProtocol p, ProtocolList ps) => ProtocolList (p ': ps) where protocolList = protocolVal @p : protocolList @ps