module Text.Show.Text.TH.Internal (
deriveShow
, deriveShowPragmas
, mkShow
, mkShowLazy
, mkShowPrec
, mkShowPrecLazy
, mkShowList
, mkShowListLazy
, mkShowb
, mkShowbPrec
, mkShowbList
, mkPrint
, mkPrintLazy
, mkHPrint
, mkHPrintLazy
, PragmaOptions(..)
, defaultPragmaOptions
, defaultInlineShowbPrec
, defaultInlineShowb
, defaultInlineShowbList
) where
import Data.Functor ((<$>))
import Data.List (foldl', intersperse)
#if MIN_VERSION_template_haskell(2,7,0)
import Data.List (find)
import Data.Maybe (fromJust)
#endif
#if !(MIN_VERSION_base(4,8,0))
import Data.Monoid (mempty)
#endif
import qualified Data.Text as TS ()
import qualified Data.Text.IO as TS (putStrLn, hPutStrLn)
import Data.Text.Lazy (toStrict)
import Data.Text.Lazy.Builder (fromString, toLazyText)
import qualified Data.Text.Lazy as TL ()
import qualified Data.Text.Lazy.IO as TL (putStrLn, hPutStrLn)
import GHC.Show (appPrec, appPrec1)
import Language.Haskell.TH
import Prelude hiding (Show)
import qualified Text.Show as S (Show(show))
import qualified Text.Show.Text.Classes as T (Show)
import Text.Show.Text.Classes (showb, showbPrec, showbListDefault,
showbParen, showbSpace)
#if __GLASGOW_HASKELL__ >= 702
import Text.Show.Text.Classes (showbList)
#endif
import Text.Show.Text.Utils ((<>), isInfixTypeCon, isTupleString, s)
deriveShow :: Name
-> Q [Dec]
deriveShow = deriveShowPragmas defaultPragmaOptions
deriveShowPragmas :: PragmaOptions
-> Name
-> Q [Dec]
deriveShowPragmas opts name = do
info <- reify name
case info of
TyConI{} -> deriveShowTyCon opts name
#if MIN_VERSION_template_haskell(2,7,0)
DataConI{} -> deriveShowDataFamInst opts name
FamilyI (FamilyD DataFam _ _ _) _ -> deriveShowDataFam opts name
FamilyI (FamilyD TypeFam _ _ _) _ -> error $ ns ++ "Cannot use a type family name."
_ -> error $ ns ++ "The name must be of a plain type constructor, data family, or data family instance constructor."
#else
_ -> error $ ns ++ "The name must be of a plain type constructor."
#endif
where
ns :: String
ns = "Text.Show.Text.TH.deriveShow: "
deriveShowTyCon :: PragmaOptions
-> Name
-> Q [Dec]
deriveShowTyCon opts tyConName = withTyCon tyConName fromCons
where
fromCons :: [TyVarBndr] -> [Con] -> Q [Dec]
fromCons tvbs cons = (:[]) <$>
instanceD (applyCon ''T.Show typeNames)
(appT (conT ''T.Show) instanceType)
(showbPrecDecs opts cons)
where
typeNames :: [Name]
typeNames = map tvbName tvbs
instanceType :: Q Type
instanceType = foldl' appT (conT tyConName) $ map varT typeNames
#if MIN_VERSION_template_haskell(2,7,0)
deriveShowDataFam :: PragmaOptions
-> Name
-> Q [Dec]
deriveShowDataFam opts dataFamName = withDataFam dataFamName $ \tvbs decs ->
flip mapM decs $ deriveShowDataFamFromDec opts dataFamName tvbs
deriveShowDataFamInst :: PragmaOptions
-> Name
-> Q [Dec]
deriveShowDataFamInst opts dataFamInstName = (:[]) <$>
withDataFamInstCon dataFamInstName (deriveShowDataFamFromDec opts)
deriveShowDataFamFromDec :: PragmaOptions
-> Name
-> [TyVarBndr]
-> Dec
-> Q Dec
deriveShowDataFamFromDec opts parentName tvbs dec =
instanceD (applyCon ''T.Show lhsTypeNames)
(appT (conT ''T.Show) instanceType)
(showbPrecDecs opts $ decCons [dec])
where
typeNames :: [Name]
typeNames = map tvbName tvbs
lhsTypeNames :: [Name]
# if !(MIN_VERSION_template_haskell(2,9,0)) || MIN_VERSION_template_haskell(2,10,0)
lhsTypeNames = filterTyVars typeNames instTypes
# else
lhsTypeNames = filterTyVars (take (length instTypes) typeNames) instTypes
++ drop (length instTypes) typeNames
# endif
filterTyVars :: [Name] -> [Type] -> [Name]
filterTyVars ns (SigT t _:ts) = filterTyVars ns (t:ts)
filterTyVars (_:ns) (VarT n :ts) = n : filterTyVars ns ts
filterTyVars (_:ns) (_ :ts) = filterTyVars ns ts
filterTyVars [] _ = []
filterTyVars _ [] = []
rhsTypes :: [Type]
rhsTypes = instTypes ++ drop (length instTypes) (map VarT typeNames)
instTypes :: [Type]
instTypes = let tys = case dec of
DataInstD _ _ tys' _ _ -> tys'
NewtypeInstD _ _ tys' _ _ -> tys'
_ -> error "Text.Show.Text.TH.deriveShow: The impossible happened."
# if MIN_VERSION_template_haskell(2,10,0)
in tys
# else
in if length tys > length tvbs
then drop (length tvbs) tys
else tys
# endif
instanceType :: Q Type
instanceType = foldl' appT (conT parentName) $ map return rhsTypes
#endif
mkShow :: Name -> Q Exp
mkShow name = [| toStrict . $(mkShowLazy name) |]
mkShowLazy :: Name -> Q Exp
mkShowLazy name = [| toLazyText . $(mkShowb name) |]
mkShowPrec :: Name -> Q Exp
mkShowPrec name = [| \p -> toStrict . $(mkShowPrecLazy name) p |]
mkShowPrecLazy :: Name -> Q Exp
mkShowPrecLazy name = [| \p -> toLazyText . $(mkShowbPrec name) p |]
mkShowList :: Name -> Q Exp
mkShowList name = [| toStrict . $(mkShowListLazy name) |]
mkShowListLazy :: Name -> Q Exp
mkShowListLazy name = [| toLazyText . $(mkShowbList name) |]
mkShowb :: Name -> Q Exp
mkShowb name = mkShowbPrec name `appE` [| 0 :: Int |]
mkShowbPrec :: Name -> Q Exp
mkShowbPrec name = do
info <- reify name
case info of
TyConI{} -> withTyCon name $ \_ decs -> consToShow decs
#if MIN_VERSION_template_haskell(2,7,0)
DataConI{} -> withDataFamInstCon name $ \_ _ dec ->
consToShow $ decCons [dec]
FamilyI (FamilyD DataFam _ _ _) _ -> withDataFam name $ \_ decs ->
consToShow $ decCons decs
FamilyI (FamilyD TypeFam _ _ _) _ -> error $ ns ++ "Cannot use a type family name."
_ -> error $ ns ++ "The name must be of a plain type constructor, data family, or data family instance constructor."
#else
_ -> error $ ns ++ "The name must be of a plain type constructor."
#endif
where
ns :: String
ns = "Text.Show.Text.TH.mk: "
mkShowbList :: Name -> Q Exp
mkShowbList name = [| showbListDefault $(mkShowb name) |]
mkPrint :: Name -> Q Exp
mkPrint name = [| TS.putStrLn . $(mkShow name) |]
mkPrintLazy :: Name -> Q Exp
mkPrintLazy name = [| TL.putStrLn . $(mkShowLazy name) |]
mkHPrint :: Name -> Q Exp
mkHPrint name = [| \h -> TS.hPutStrLn h . $(mkShow name) |]
mkHPrintLazy :: Name -> Q Exp
mkHPrintLazy name = [| \h -> TL.hPutStrLn h . $(mkShowLazy name) |]
data PragmaOptions = PragmaOptions {
inlineShowbPrec :: Bool
, inlineShowb :: Bool
, inlineShowbList :: Bool
, specializeTypes :: [Q Type]
}
defaultPragmaOptions :: PragmaOptions
defaultPragmaOptions = PragmaOptions False False False []
defaultInlineShowbPrec :: PragmaOptions
defaultInlineShowbPrec = defaultPragmaOptions { inlineShowbPrec = True }
defaultInlineShowb :: PragmaOptions
defaultInlineShowb = defaultPragmaOptions { inlineShowb = True }
defaultInlineShowbList :: PragmaOptions
defaultInlineShowbList = defaultPragmaOptions { inlineShowbList = True }
consToShow :: [Con] -> Q Exp
consToShow [] = error $ "Text.Show.Text.TH.consToShow: Not a single constructor given!"
consToShow cons = do
p <- newName "p"
value <- newName "value"
lam1E (varP p)
. lam1E (varP value)
. caseE (varE value)
$ map (encodeArgs p) cons
encodeArgs :: Name -> Con -> Q Match
encodeArgs p (NormalC conName [])
= match (conP conName [])
(normalB [| intConst (fromString $(stringE (nameBase conName))) $(varE p) |])
[]
encodeArgs p (NormalC conName [_]) = do
arg <- newName "arg"
let showArg = [| showbPrec appPrec1 $(varE arg) |]
namedArg = [| fromString $(stringE (nameBase conName)) <> showbSpace <> $(showArg) |]
match (conP conName [varP arg])
(normalB [| showbParen ($(varE p) > appPrec) $(namedArg) |])
[]
encodeArgs p (NormalC conName ts) = do
args <- mapM newName ["arg" ++ S.show n | (_, n) <- zip ts [1 :: Int ..]]
if isNonUnitTuple conName
then do
let showArgs = map (appE [| showb |] . varE) args
parenCommaArgs = [| s '(' |] : intersperse [| s ',' |] showArgs
mappendArgs = foldr (flip infixApp [| (<>) |])
[| s ')' |]
parenCommaArgs
match (conP conName $ map varP args)
(normalB [| intConst $(mappendArgs) $(varE p) |])
[]
else do
let showArgs = map (appE [| showbPrec appPrec1 |] . varE) args
mappendArgs = foldr1 (\v q -> [| $(v) <> showbSpace <> $(q) |]) showArgs
namedArgs = [| fromString $(stringE (nameBase conName)) <> showbSpace <> $(mappendArgs) |]
match (conP conName $ map varP args)
(normalB [| showbParen ($(varE p) > appPrec) $(namedArgs) |])
[]
encodeArgs p (RecC conName []) = encodeArgs p $ NormalC conName []
encodeArgs p (RecC conName ts) = do
args <- mapM newName ["arg" ++ S.show n | (_, n) <- zip ts [1 :: Int ..]]
let showArgs = concatMap (\(arg, (argName, _, _))
-> [ [| fromString $(stringE (nameBase argName)) |]
, [| fromString " = " |]
, [| showb $(varE arg) |]
, [| fromString ", " |]
]
)
(zip args ts)
braceCommaArgs = [| s '{' |] : take (length showArgs 1) showArgs
mappendArgs = foldr (flip infixApp [| (<>) |])
[| s '}' |]
braceCommaArgs
namedArgs = [| fromString $(stringE (nameBase conName)) <> showbSpace <> $(mappendArgs) |]
match (conP conName $ map varP args)
(normalB [| showbParen ($(varE p) > appPrec) $(namedArgs) |])
[]
encodeArgs p (InfixC _ conName _) = do
al <- newName "argL"
ar <- newName "argR"
info <- reify conName
let conPrec = case info of
DataConI _ _ _ (Fixity prec _) -> prec
other -> error $ "Text.Show.Text.TH.encodeArgs: Unsupported type: " ++ S.show other
opNameE = stringE $ nameBase conName
mBacktickE = [| if isInfixTypeCon $(opNameE)
then mempty
else s '`'
|]
match (infixP (varP al) conName (varP ar))
(normalB $ appE [| showbParen ($(varE p) > conPrec) |]
[| showbPrec (conPrec + 1) $(varE al)
<> showbSpace
<> $(mBacktickE)
<> fromString $(opNameE)
<> $(mBacktickE)
<> showbSpace
<> showbPrec (conPrec + 1) $(varE ar)
|]
)
[]
encodeArgs p (ForallC _ _ con) = encodeArgs p con
isNonUnitTuple :: Name -> Bool
isNonUnitTuple = isTupleString . nameBase
intConst :: a -> Int -> a
intConst = const
withTyCon :: Name
-> ([TyVarBndr] -> [Con] -> Q a)
-> Q a
withTyCon name f = do
info <- reify name
case info of
TyConI dec ->
case dec of
DataD _ _ tvbs cons _ -> f tvbs cons
NewtypeD _ _ tvbs con _ -> f tvbs [con]
other -> error $ ns ++ "Unsupported type " ++ S.show other ++ ". Must be a data type or newtype."
_ -> error $ ns ++ "The name must be of a plain type constructor."
where
ns :: String
ns = "Text.Show.Text.TH.withTyCon: "
#if MIN_VERSION_template_haskell(2,7,0)
withDataFam :: Name
-> ([TyVarBndr] -> [Dec] -> Q a)
-> Q a
withDataFam name f = do
info <- reify name
case info of
FamilyI (FamilyD DataFam _ tvbs _) decs -> f tvbs decs
FamilyI (FamilyD TypeFam _ _ _) _ ->
error $ ns ++ "Cannot use a type family name."
other -> error $ ns ++ "Unsupported type " ++ S.show other ++ ". Must be a data family name."
where
ns :: String
ns = "Text.Show.Text.TH.withDataFam: "
withDataFamInstCon :: Name
-> (Name -> [TyVarBndr] -> Dec -> Q a)
-> Q a
withDataFamInstCon dficName f = do
dficInfo <- reify dficName
case dficInfo of
DataConI _ _ parentName _ -> do
parentInfo <- reify parentName
case parentInfo of
FamilyI (FamilyD DataFam _ _ _) _ -> withDataFam parentName $ \tvbs decs ->
let sameDefDec = fromJust . flip find decs $ \dec ->
case dec of
DataInstD _ _ _ cons _ -> any ((dficName ==) . constructorName) cons
NewtypeInstD _ _ _ con _ -> dficName == constructorName con
_ -> error $ ns ++ "Must be a data or newtype instance."
in f parentName tvbs sameDefDec
_ -> error $ ns ++ "Data constructor " ++ S.show dficName ++ " is not from a data family instance."
other -> error $ ns ++ "Unsupported type " ++ S.show other ++ ". Must be a data family instance constructor."
where
ns :: String
ns = "Text.Show.Text.TH.withDataFamInstCon: "
#endif
decCons :: [Dec] -> [Con]
decCons decs = flip concatMap decs $ \dec -> case dec of
DataInstD _ _ _ cons _ -> cons
NewtypeInstD _ _ _ con _ -> [con]
_ -> error $ "Text.Show.Text.TH.decCons: Must be a data or newtype instance."
constructorName :: Con -> Name
constructorName (NormalC name _ ) = name
constructorName (RecC name _ ) = name
constructorName (InfixC _ name _ ) = name
constructorName (ForallC _ _ con) = constructorName con
tvbName :: TyVarBndr -> Name
tvbName (PlainTV name ) = name
tvbName (KindedTV name _) = name
applyCon :: Name -> [Name] -> Q [Pred]
applyCon con typeNames = return $ map apply typeNames
where
apply :: Name -> Pred
apply t =
#if MIN_VERSION_template_haskell(2,10,0)
AppT (ConT con) (VarT t)
#else
ClassP con [VarT t]
#endif
showbPrecDecs :: PragmaOptions -> [Con] -> [Q Dec]
#if __GLASGOW_HASKELL__ >= 702
showbPrecDecs opts cons =
#else
showbPrecDecs _ cons =
#endif
[ funD 'showbPrec [ clause []
(normalB $ consToShow cons)
[]
]
] ++ inlineShowbPrecDec
++ inlineShowbDec
++ inlineShowbListDec
++ specializeDecs
where
inlineShowbPrecDec, inlineShowbDec, inlineShowbListDec :: [Q Dec]
#if __GLASGOW_HASKELL__ >= 702
inlineShowbPrecDec = inline inlineShowbPrec 'showbPrec
inlineShowbDec = inline inlineShowb 'showb
inlineShowbListDec = inline inlineShowbList 'showbList
#else
inlineShowbPrecDec = []
inlineShowbDec = []
inlineShowbListDec = []
#endif
#if __GLASGOW_HASKELL__ >= 702
inline :: (PragmaOptions -> Bool) -> Name -> [Q Dec]
inline isInlining funName
| isInlining opts = [ pragInlD funName
# if MIN_VERSION_template_haskell(2,8,0)
Inline FunLike AllPhases
# else
(inlineSpecNoPhase True False)
# endif
]
| otherwise = []
#endif
specializeDecs :: [Q Dec]
#if MIN_VERSION_template_haskell(2,8,0)
specializeDecs = (map . fmap) (PragmaD
. SpecialiseInstP
. AppT (ConT ''T.Show)
)
(specializeTypes opts)
#else
specializeDecs = []
#endif