{-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} -- | You want to add formatting support for your custom type. This is the right module. -- -- In PyF, formatters are in three categories: -- -- - Integral numbers, which are numbers without fractional part -- - Fractional numbers, which are numbers with a fractional part -- - String, which represents text. -- -- The formatting can be either explicit or implicit. For example: -- -- >>> let x = 10 in [fmt|{x}|] -- 10 -- -- Is an implicit formating to number, but: -- -- >>> let x = 10 in [fmt|{x:d}|] -- -- Is an explicit formatting to Integral numbers, using @d@. -- -- Implicit formatting will only format to either Integral, Fractional or text, -- and this choice is done by the (open) type family `PyFCategory'. -- -- This modules also provides 3 type class for formatting. -- -- - 'PyfFormatFractional' and 'PyfFormatIntegral' are responsible for -- formatting integral and fractional numbers. Default instances are provided -- respectively for 'Real' and 'Integral'. 'PyFToString' is in charge of -- formatting text. module PyF.Class where import qualified Data.ByteString import qualified Data.ByteString.Lazy import Data.Char (ord) import Data.Int import qualified Data.Ratio import qualified Data.Text as SText import qualified Data.Text.Encoding as E import qualified Data.Text.Lazy as LText import qualified Data.Time import Data.Word import Numeric.Natural import PyF.Formatters -- * Default formatting classification -- | The three categories of formatting in 'PyF' data PyFCategory = -- | Format as an integral, no fractional part, precise value PyFIntegral | -- | Format as a fractional, approximate value with a fractional part PyFFractional | -- | Format as a string PyFString -- | Classify a type to a 'PyFCategory' -- This classification will be used to decide which formatting to -- use when no type specifier in provided. type family PyFClassify t :: PyFCategory type instance PyFClassify Integer = 'PyFIntegral type instance PyFClassify Int = 'PyFIntegral type instance PyFClassify Int8 = 'PyFIntegral type instance PyFClassify Int16 = 'PyFIntegral type instance PyFClassify Int32 = 'PyFIntegral type instance PyFClassify Int64 = 'PyFIntegral type instance PyFClassify Natural = 'PyFIntegral type instance PyFClassify Word = 'PyFIntegral type instance PyFClassify Word8 = 'PyFIntegral type instance PyFClassify Word16 = 'PyFIntegral type instance PyFClassify Word32 = 'PyFIntegral type instance PyFClassify Word64 = 'PyFIntegral -- Float numbers type instance PyFClassify Float = 'PyFFractional type instance PyFClassify Double = 'PyFFractional type instance PyFClassify Data.Time.DiffTime = 'PyFFractional type instance PyFClassify Data.Time.NominalDiffTime = 'PyFFractional type instance PyFClassify (Data.Ratio.Ratio i) = 'PyFFractional -- String type instance PyFClassify String = 'PyFString type instance PyFClassify LText.Text = 'PyFString type instance PyFClassify SText.Text = 'PyFString type instance PyFClassify Data.ByteString.ByteString = 'PyFString type instance PyFClassify Data.ByteString.Lazy.ByteString = 'PyFString type instance PyFClassify Char = 'PyFString -- * String formatting -- | Convert a type to string -- This is used for the string formatting. class PyFToString t where pyfToString :: t -> String instance PyFToString String where pyfToString = id instance PyFToString LText.Text where pyfToString = LText.unpack instance PyFToString SText.Text where pyfToString = SText.unpack instance PyFToString Data.ByteString.ByteString where pyfToString = SText.unpack . E.decodeUtf8 instance PyFToString Data.ByteString.Lazy.ByteString where pyfToString = pyfToString . Data.ByteString.Lazy.toStrict instance PyFToString Char where pyfToString c = [c] -- | Default instance. Convert any type with a 'Show instance. instance {-# OVERLAPPABLE #-} Show t => PyFToString t where pyfToString = show -- * Real formatting (with optional fractional part) -- | Apply a fractional formatting to any type. -- -- A default instance for any 'Real' is provided which internally converts to -- 'Double', which may not be efficient or results in rounding errors. -- -- You can provide your own instance and internally use 'formatFractional' -- which does have the same signatures as 'pyfFormatFractional' but with a -- 'RealFrac' constraint. class PyfFormatFractional a where pyfFormatFractional :: Format t t' 'Fractional -> -- | Sign formatting SignMode -> -- | Padding Maybe (Int, AlignMode k, Char) -> -- | Grouping Maybe (Int, Char) -> -- | Precision Maybe Int -> a -> String -- | Default instance working for any 'Real'. Internally it converts the type to 'Double'. instance {-# OVERLAPPABLE #-} Real t => PyfFormatFractional t where pyfFormatFractional f s p g prec v = formatFractional f s p g prec (realToFrac @t @Double v) -- | This instance does not do any conversion. instance PyfFormatFractional Double where pyfFormatFractional = formatFractional -- | This instance does not do any conversion. instance PyfFormatFractional Float where pyfFormatFractional = formatFractional -- * Integral formatting -- | Apply an integral formatting to any type. -- -- A default instance for any 'Integral' is provided. -- -- You can provide your own instance and internally use 'formatIntegral' -- which does have the same signatures as 'pyfFormatIntegral' but with an -- 'Integral' constraint. class PyfFormatIntegral i where pyfFormatIntegral :: Format t t' 'Integral -> -- | Sign formatting SignMode -> -- | Padding Maybe (Int, AlignMode k, Char) -> -- | Grouping Maybe (Int, Char) -> i -> String -- | Default instance for any 'Integral'. instance {-# OVERLAPPABLE #-} Integral t => PyfFormatIntegral t where pyfFormatIntegral f s p g v = formatIntegral f s p g v -- | Returns the numerical value of a 'Char' -- >>> [fmt|{'a':d}|] -- 97 instance PyfFormatIntegral Char where pyfFormatIntegral f s p g v = formatIntegral f s p g (ord v)