{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeSynonymInstances #-} #if __GLASGOW_HASKELL__ >= 702 {-# LANGUAGE DeriveGeneric #-} #endif #if __GLASGOW_HASKELL__ >= 706 {-# LANGUAGE PolyKinds #-} #endif #if __GLASGOW_HASKELL__ >= 800 {-# LANGUAGE DeriveLift #-} #endif {-| Module: TextShow.Generic Copyright: (C) 2014-2016 Ryan Scott License: BSD-style (see the file LICENSE) Maintainer: Ryan Scott Stability: Provisional Portability: GHC Generic versions of 'TextShow' and 'TextShow1' class functions, as an alternative to "TextShow.TH", which uses Template Haskell. Because there is no 'Generic2' class, 'TextShow2' cannot be implemented generically. This implementation is loosely based off of the @Generics.Deriving.Show@ module from the @generic-deriving@ library. /Since: 2/ -} module TextShow.Generic ( -- * Generic @show@ functions -- $generics -- ** Understanding a compiler error -- $generic_err genericShowt , genericShowtl , genericShowtPrec , genericShowtlPrec , genericShowtList , genericShowtlList , genericShowb , genericShowbPrec , genericShowbList , genericPrintT , genericPrintTL , genericHPrintT , genericHPrintTL , genericLiftShowbPrec , genericShowbPrec1 -- * 'GTextShow' and friends , GTextShow(..) , GTextShowCon(..) , IsNullary(..) , ConType(..) , Zero , One ) where import Data.Monoid.Compat ((<>)) import Data.Proxy (Proxy(..)) import qualified Data.Text as TS (Text) import qualified Data.Text.IO as TS (putStrLn, hPutStrLn) import Data.Text.Lazy (toStrict) import Data.Text.Lazy.Builder (Builder, fromString, singleton, toLazyText) import qualified Data.Text.Lazy as TL (Text) import qualified Data.Text.Lazy.IO as TL (putStrLn, hPutStrLn) import Data.Typeable (Typeable) import Generics.Deriving.Base #if __GLASGOW_HASKELL__ < 702 import qualified Generics.Deriving.TH as Generics (deriveAll) #endif import GHC.Exts (Char(C#), Double(D#), Float(F#), Int(I#), Word(W#)) import GHC.Show (appPrec, appPrec1) import Language.Haskell.TH.Lift import Prelude () import Prelude.Compat import System.IO (Handle) import TextShow.Classes (TextShow(..), TextShow1(..), showbListWith, showbParen, showbSpace) import TextShow.Instances () import TextShow.Utils (isInfixTypeCon, isTupleString) #include "inline.h" {- $generics 'TextShow' instances can be easily defined for data types that are 'Generic' instances. The easiest way to do this is to use the @DeriveGeneric@ extension. @ {-# LANGUAGE DeriveGeneric #-} import GHC.Generics import TextShow import TextShow.Generic data D a = D a deriving (Generic, Generic1) instance TextShow a => TextShow (D a) where showbPrec = 'genericShowbPrec' instance TextShow1 D where liftShowbPrec = 'genericLiftShowbPrec' @ -} {- $generic_err Suppose you intend to use 'genericShowbPrec' to define a 'TextShow' instance. @ data Oops = Oops -- forgot to add \"deriving Generic\" here! instance TextShow Oops where showbPrec = 'genericShowbPrec' @ If you forget to add a @deriving 'Generic'@ clause to your data type, at compile-time, you might get an error message that begins roughly as follows: @ No instance for ('GTextShow' 'Zero' (Rep Oops)) @ This error can be confusing, but don't let it intimidate you. The correct fix is simply to add the missing \"@deriving 'Generic'@\" clause. Similarly, if the compiler complains about not having an instance for @('GTextShow' 'One' (Rep1 Oops1))@, add a \"@deriving 'Generic1'@\" clause. -} -- | A 'Generic' implementation of 'showt'. -- -- /Since: 2/ genericShowt :: (Generic a, GTextShow Zero (Rep a)) => a -> TS.Text genericShowt = toStrict . genericShowtl -- | A 'Generic' implementation of 'showtl'. -- -- /Since: 2/ genericShowtl :: (Generic a, GTextShow Zero (Rep a)) => a -> TL.Text genericShowtl = toLazyText . genericShowb -- | A 'Generic' implementation of 'showPrect'. -- -- /Since: 2/ genericShowtPrec :: (Generic a, GTextShow Zero (Rep a)) => Int -> a -> TS.Text genericShowtPrec p = toStrict . genericShowtlPrec p -- | A 'Generic' implementation of 'showtlPrec'. -- -- /Since: 2/ genericShowtlPrec :: (Generic a, GTextShow Zero (Rep a)) => Int -> a -> TL.Text genericShowtlPrec p = toLazyText . genericShowbPrec p -- | A 'Generic' implementation of 'showtList'. -- -- /Since: 2/ genericShowtList :: (Generic a, GTextShow Zero (Rep a)) => [a] -> TS.Text genericShowtList = toStrict . genericShowtlList -- | A 'Generic' implementation of 'showtlList'. -- -- /Since: 2/ genericShowtlList :: (Generic a, GTextShow Zero (Rep a)) => [a] -> TL.Text genericShowtlList = toLazyText . genericShowbList -- | A 'Generic' implementation of 'showb'. -- -- /Since: 2/ genericShowb :: (Generic a, GTextShow Zero (Rep a)) => a -> Builder genericShowb = genericShowbPrec 0 -- | A 'Generic' implementation of 'showbPrec'. -- -- /Since: 2/ genericShowbPrec :: (Generic a, GTextShow Zero (Rep a)) => Int -> a -> Builder genericShowbPrec p = gShowbPrec (Proxy :: Proxy Zero) undefined undefined p . from -- | A 'Generic' implementation of 'showbList'. -- -- /Since: 2/ genericShowbList :: (Generic a, GTextShow Zero (Rep a)) => [a] -> Builder genericShowbList = showbListWith genericShowb -- | A 'Generic' implementation of 'printT'. -- -- /Since: 2/ genericPrintT :: (Generic a, GTextShow Zero (Rep a)) => a -> IO () genericPrintT = TS.putStrLn . genericShowt -- | A 'Generic' implementation of 'printTL'. -- -- /Since: 2/ genericPrintTL :: (Generic a, GTextShow Zero (Rep a)) => a -> IO () genericPrintTL = TL.putStrLn . genericShowtl -- | A 'Generic' implementation of 'hPrintT'. -- -- /Since: 2/ genericHPrintT :: (Generic a, GTextShow Zero (Rep a)) => Handle -> a -> IO () genericHPrintT h = TS.hPutStrLn h . genericShowt -- | A 'Generic' implementation of 'hPrintTL'. -- -- /Since: 2/ genericHPrintTL :: (Generic a, GTextShow Zero (Rep a)) => Handle -> a -> IO () genericHPrintTL h = TL.hPutStrLn h . genericShowtl -- | A 'Generic1' implementation of 'genericLiftShowbPrec'. -- -- /Since: 2/ genericLiftShowbPrec :: (Generic1 f, GTextShow One (Rep1 f)) => (Int -> a -> Builder) -> ([a] -> Builder) -> Int -> f a -> Builder genericLiftShowbPrec sp sl p = gShowbPrec (Proxy :: Proxy One) sp sl p . from1 -- | A 'Generic'/'Generic1' implementation of 'showbPrec1'. -- -- /Since: 2/ genericShowbPrec1 :: ( Generic a, Generic1 f , GTextShow Zero (Rep a) , GTextShow One (Rep1 f) ) => Int -> f a -> Builder genericShowbPrec1 = genericLiftShowbPrec genericShowbPrec genericShowbList ------------------------------------------------------------------------------- -- | Whether a constructor is a record ('Rec'), a tuple ('Tup'), is prefix ('Pref'), -- or infix ('Inf'). -- -- /Since: 2/ data ConType = Rec | Tup | Pref | Inf String deriving ( Eq , Ord , Read , Show , Typeable #if __GLASGOW_HASKELL__ >= 702 , Generic #endif #if __GLASGOW_HASKELL__ >= 800 , Lift #endif ) instance TextShow ConType where showbPrec = genericShowbPrec INLINE_INST_FUN(showbPrec) -- | A type-level indicator that 'TextShow' is being derived generically. -- -- / Since: 3.2/ data Zero -- | A type-level indicator that 'TextShow1' is being derived generically. -- -- / Since: 3.2/ data One -- | Class of generic representation types that can be converted to -- a 'Builder'. The @arity@ type variable indicates which type class is -- used. @'GTextShow' 'Zero'@ indicates 'TextShow' behavior, and -- @'GTextShow' 'One'@ indicates 'TextShow1' behavior. -- -- /Since: 3.2/ class GTextShow arity f where -- | This is used as the default generic implementation of 'showbPrec' (if the -- @arity@ is 'Zero') or 'liftShowbPrec' (if the @arity@ is 'One'). gShowbPrec :: Proxy arity -> (Int -> a -> Builder) -> ([a] -> Builder) -> Int -> f a -> Builder #if __GLASGOW_HASKELL__ >= 708 deriving instance Typeable GTextShow #endif instance GTextShow arity f => GTextShow arity (D1 d f) where gShowbPrec pa sp sl p (M1 x) = gShowbPrec pa sp sl p x instance GTextShow Zero V1 where gShowbPrec _ _ _ _ !_ = error "Void showbPrec" instance GTextShow One V1 where gShowbPrec _ _ _ _ !_ = error "Void liftShowbPrec" instance (GTextShow arity f, GTextShow arity g) => GTextShow arity (f :+: g) where gShowbPrec pa sp sl p (L1 x) = gShowbPrec pa sp sl p x gShowbPrec pa sp sl p (R1 x) = gShowbPrec pa sp sl p x instance (Constructor c, GTextShowCon arity f, IsNullary f) => GTextShow arity (C1 c f) where gShowbPrec pa sp sl p c@(M1 x) = case fixity of Prefix -> showbParen ( p > appPrec && not (isNullary x || conIsTuple c) ) $ (if conIsTuple c then mempty else let cn = conName c in showbParen (isInfixTypeCon cn) $ fromString cn) <> (if isNullary x || conIsTuple c then mempty else singleton ' ') <> showbBraces t (gShowbPrecCon pa t sp sl appPrec1 x) Infix _ m -> showbParen (p > m) $ gShowbPrecCon pa t sp sl (m+1) x where fixity :: Fixity fixity = conFixity c t :: ConType t = if conIsRecord c then Rec else case conIsTuple c of True -> Tup False -> case fixity of Prefix -> Pref Infix _ _ -> Inf $ conName c showbBraces :: ConType -> Builder -> Builder showbBraces Rec b = singleton '{' <> b <> singleton '}' showbBraces Tup b = singleton '(' <> b <> singleton ')' showbBraces Pref b = b showbBraces (Inf _) b = b conIsTuple :: C1 c f p -> Bool conIsTuple = isTupleString . conName -- | Class of generic representation types for which the 'ConType' has been -- determined. The @arity@ type variable indicates which type class is -- used. @'GTextShow' 'Zero'@ indicates 'TextShow' behavior, and -- @'GTextShow' 'One'@ indicates 'TextShow1' behavior. class GTextShowCon arity f where -- | Convert value of a specific 'ConType' to a 'Builder' with the given -- precedence. gShowbPrecCon :: Proxy arity -> ConType -> (Int -> a -> Builder) -> ([a] -> Builder) -> Int -> f a -> Builder #if __GLASGOW_HASKELL__ >= 708 deriving instance Typeable GTextShowCon #endif instance GTextShowCon arity U1 where gShowbPrecCon _ _ _ _ _ U1 = mempty instance GTextShowCon One Par1 where gShowbPrecCon _ _ sp _ p (Par1 x) = sp p x instance TextShow c => GTextShowCon arity (K1 i c) where gShowbPrecCon _ _ _ _ p (K1 x) = showbPrec p x instance TextShow1 f => GTextShowCon One (Rec1 f) where gShowbPrecCon _ _ sp sl p (Rec1 x) = liftShowbPrec sp sl p x instance (Selector s, GTextShowCon arity f) => GTextShowCon arity (S1 s f) where gShowbPrecCon pa t sp sl p sel@(M1 x) | selName sel == "" = gShowbPrecCon pa t sp sl p x | otherwise = fromString (selName sel) <> " = " <> gShowbPrecCon pa t sp sl 0 x instance (GTextShowCon arity f, GTextShowCon arity g) => GTextShowCon arity (f :*: g) where gShowbPrecCon pa t@Rec sp sl _ (a :*: b) = gShowbPrecCon pa t sp sl 0 a <> ", " <> gShowbPrecCon pa t sp sl 0 b gShowbPrecCon pa t@(Inf o) sp sl p (a :*: b) = gShowbPrecCon pa t sp sl p a <> showbSpace <> infixOp <> showbSpace <> gShowbPrecCon pa t sp sl p b where infixOp :: Builder infixOp = if isInfixTypeCon o then fromString o else singleton '`' <> fromString o <> singleton '`' gShowbPrecCon pa t@Tup sp sl _ (a :*: b) = gShowbPrecCon pa t sp sl 0 a <> singleton ',' <> gShowbPrecCon pa t sp sl 0 b gShowbPrecCon pa t@Pref sp sl p (a :*: b) = gShowbPrecCon pa t sp sl p a <> showbSpace <> gShowbPrecCon pa t sp sl p b instance (TextShow1 f, GTextShowCon One g) => GTextShowCon One (f :.: g) where gShowbPrecCon pa t sp sl p (Comp1 x) = liftShowbPrec (gShowbPrecCon pa t sp sl) (showbListWith (gShowbPrecCon pa t sp sl 0)) p x instance GTextShowCon arity UChar where gShowbPrecCon _ _ _ _ p (UChar c) = showbPrec (hashPrec p) (C# c) <> oneHash instance GTextShowCon arity UDouble where gShowbPrecCon _ _ _ _ p (UDouble d) = showbPrec (hashPrec p) (D# d) <> twoHash instance GTextShowCon arity UFloat where gShowbPrecCon _ _ _ _ p (UFloat f) = showbPrec (hashPrec p) (F# f) <> oneHash instance GTextShowCon arity UInt where gShowbPrecCon _ _ _ _ p (UInt i) = showbPrec (hashPrec p) (I# i) <> oneHash instance GTextShowCon arity UWord where gShowbPrecCon _ _ _ _ p (UWord w) = showbPrec (hashPrec p) (W# w) <> twoHash oneHash, twoHash :: Builder hashPrec :: Int -> Int #if __GLASGOW_HASKELL__ >= 711 oneHash = singleton '#' twoHash = fromString "##" hashPrec = const 0 #else oneHash = mempty twoHash = mempty hashPrec = id #endif -- | Class of generic representation types that represent a constructor with -- zero or more fields. class IsNullary f where -- Returns 'True' if the constructor has no fields. isNullary :: f a -> Bool instance IsNullary U1 where isNullary _ = True instance IsNullary Par1 where isNullary _ = False instance IsNullary (K1 i c) where isNullary _ = False instance IsNullary f => IsNullary (S1 s f) where isNullary (M1 x) = isNullary x instance IsNullary (Rec1 f) where isNullary _ = False instance IsNullary (f :*: g) where isNullary _ = False instance IsNullary (f :.: g) where isNullary _ = False instance IsNullary UChar where isNullary _ = False instance IsNullary UDouble where isNullary _ = False instance IsNullary UFloat where isNullary _ = False instance IsNullary UInt where isNullary _ = False instance IsNullary UWord where isNullary _ = False ------------------------------------------------------------------------------- #if __GLASGOW_HASKELL__ < 702 $(Generics.deriveAll ''ConType) #endif #if __GLASGOW_HASKELL__ < 800 $(deriveLift ''ConType) #endif