{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE PolyKinds #-} module Elminator.Generics.Simple where import Control.Monad.State.Strict import qualified Data.List as DL import qualified Data.Map.Strict as DMS import Data.Proxy import Data.String import Data.Text as T import GHC.Generics import GHC.TypeLits import Language.Haskell.TH newtype CName = CName Text deriving (Show) data HField = HField (Maybe Text) HType deriving (Show) type HState = State (DMS.Map MData ()) type ExTypeName = Text type ExEncoderName = Text type ExDecoderName = Text type ModuleName = Text type SymbolName = Text type ExItem = (ModuleName, SymbolName) data ExInfo a = ExInfo { exType :: ExItem , exEncoder :: Maybe ExItem , exDecoder :: Maybe ExItem , exTypeArgs :: [a] } deriving (Show) data MData = MData { _mTypeName :: Text , _mModuleName :: Text , _mPackageName :: Text } deriving (Show, Ord, Eq) instance IsString MData where fromString x = MData (pack x) "" "" data HConstructor = HConstructor CName [HField] deriving (Show) data TypeVar = Used Name | Phantom Name deriving (Eq, Show) data UDefData = UDefData { udefdMdata :: MData , udefdTypeArgs :: [HType] , udefDConstructors :: [HConstructor] } deriving (Show) -- | This type holds the type information we get from generics. -- Only the `HExternal` constructor is supposed to be used by the programmer -- to implement `ToHType` instances for entites that are predefined in Elm. A sample can be seen below. -- -- Here, let `MyExtType a b` be a type which has the corresponding type, encoders and decoders predefined in Elm -- in a module named "Lib". Here is how you can implement a ToHType instance for this type so that your other -- autogenerated types can have fields of type `MyExtType a b`. -- -- @ -- -- instance (ToHType a, ToHType b) => ToHType (MyExtType a b) where -- toHType _ = do -- ha <- toHType (Proxy :: Proxy a) -- hb <- toHType (Proxy :: Proxy b) -- pure $ -- HExternal -- (ExInfo -- ("External", "MyExtType") -- (Just ("External", "encodeMyExtType")) -- (Just ("External", "decodeMyExtType")) -- [ha, hb]) -- -- @ -- data HType = HUDef UDefData | HMaybe HType | HList HType | HPrimitive MData | HRecursive MData | HExternal (ExInfo HType) deriving (Show) class ToHType_ (f :: * -> *) where toHType_ :: Proxy f -> HState HType class ToHField_ (f :: * -> *) where toHField_ :: Proxy f -> HState [HField] class ToHConstructor_ (f :: * -> *) where toHConstructor_ :: Proxy f -> HState [HConstructor] type family ExtractTArgs (f :: k) :: [*] where ExtractTArgs ((b :: * -> k) a) = a : ExtractTArgs b ExtractTArgs f = '[] class ToHTArgs f where toHTArgs :: Proxy f -> [HState HType] instance ToHTArgs '[] where toHTArgs _ = [] instance (ToHType a, ToHTArgs x) => ToHTArgs (a : x) where toHTArgs _ = toHType (Proxy :: Proxy a) : toHTArgs (Proxy :: Proxy x) class ToHType f where toHType :: Proxy f -> HState HType default toHType :: (ToHTArgs (ExtractTArgs f), Generic f, ToHType_ (Rep f)) => Proxy f -> HState HType toHType _ = do targs <- sequence (toHTArgs (Proxy :: Proxy (ExtractTArgs f))) htype <- toHType_ (Proxy :: (Proxy (Rep f))) pure $ case htype of HUDef ud -> HUDef $ ud {udefdTypeArgs = DL.reverse targs} a -> a instance (ToHConstructor_ b, KnownSymbol a1, KnownSymbol a2, KnownSymbol a3) => ToHType_ (D1 ('MetaData a1 a2 a3 a4) b) where toHType_ _ = let mdata = MData (pack $ symbolVal (Proxy :: Proxy a1)) (pack $ symbolVal (Proxy :: Proxy a2)) (pack $ symbolVal (Proxy :: Proxy a3)) in do seen <- get case DMS.lookup mdata seen of Just _ -> pure $ HRecursive mdata Nothing -> do case isTuple $ _mTypeName mdata of Just _ -> pure () Nothing -> put $ DMS.insert mdata () seen cons_ <- toHConstructor_ (Proxy :: Proxy b) pure $ HUDef $ UDefData mdata [] cons_ isTuple :: Text -> Maybe Int isTuple t = case T.uncons t of Just (c, _) -> if c == '(' then Just $ DL.length $ T.split (== ',') t else Nothing _ -> Nothing instance ToHConstructor_ V1 where toHConstructor_ _ = pure [] instance (KnownSymbol cname, ToHField_ s) => ToHConstructor_ (C1 ('MetaCons cname a b) s) where toHConstructor_ _ = do hfield <- toHField_ (Proxy :: Proxy s) pure [HConstructor (CName $ pack $ symbolVal (Proxy :: Proxy cname)) hfield] instance ToHField_ U1 where toHField_ _ = pure [] instance (KnownSymbol cname, ToHType_ w) => ToHField_ (S1 ('MetaSel ('Just cname) a b c) w) where toHField_ _ = do htype <- toHType_ (Proxy :: Proxy w) pure [HField (Just $ pack $ symbolVal (Proxy :: Proxy cname)) htype] instance (ToHType_ w) => ToHField_ (S1 ('MetaSel 'Nothing a b c) w) where toHField_ _ = do htype <- toHType_ (Proxy :: Proxy w) pure [HField Nothing htype] instance (ToHField_ a, ToHField_ b) => ToHField_ (a :*: b) where toHField_ _ = do hfield1 <- toHField_ (Proxy :: Proxy a) hfield2 <- toHField_ (Proxy :: Proxy b) pure $ hfield1 ++ hfield2 instance (ToHConstructor_ a, ToHConstructor_ b) => ToHConstructor_ (a :+: b) where toHConstructor_ _ = do lhs <- toHConstructor_ (Proxy :: Proxy a) rhs <- toHConstructor_ (Proxy :: Proxy b) pure $ lhs ++ rhs instance (ToHType a) => ToHType_ (K1 R a) where toHType_ _ = toHType (Proxy :: Proxy a) -- Primitive instances instance ToHType Char where toHType _ = pure $ HPrimitive (MData "Char" "" "") instance ToHType Int where toHType _ = pure $ HPrimitive (MData "Int" "" "") instance ToHType Float where toHType _ = pure $ HPrimitive (MData "Float" "" "") instance ToHType Bool where toHType _ = pure $ HPrimitive (MData "Bool" "" "") -- Common types instance (ToHType a, ToHType b) => ToHType (Either a b) instance (ToHType a) => ToHType (Maybe a) where toHType _ = do htype <- toHType (Proxy :: Proxy a) pure $ HMaybe htype instance ToHType () instance (ToHType a1, ToHType a2) => ToHType (a1, a2) instance (ToHType a1, ToHType a2, ToHType a3) => ToHType (a1, a2, a3) instance (ToHType a1, ToHType a2, ToHType a3, ToHType a4) => ToHType (a1, a2, a3, a4) instance (ToHType a1, ToHType a2, ToHType a3, ToHType a4, ToHType a5) => ToHType (a1, a2, a3, a4, a5) instance ( ToHType a1 , ToHType a2 , ToHType a3 , ToHType a4 , ToHType a5 , ToHType a6 ) => ToHType (a1, a2, a3, a4, a5, a6) instance ( ToHType a1 , ToHType a2 , ToHType a3 , ToHType a4 , ToHType a5 , ToHType a6 , ToHType a7 ) => ToHType (a1, a2, a3, a4, a5, a6, a7) instance (ToHType a) => ToHType [a] where toHType _ = do htype <- toHType (Proxy :: Proxy a) pure $ case htype of HPrimitive (MData "Char" _ _) -> HPrimitive (MData "String" "" "") hta -> HList hta