module Text.Printf.TH.Printer where
import Control.Monad.Fix
import Data.Monoid
import Data.String
import Numeric.Natural
import Text.ParserCombinators.ReadP (readP_to_S)
import Text.Printf.TH.Types
import Text.Read.Lex
class (IsString a, Monoid a) =>
Printer a
where
type Output a
string :: String -> a
cons :: Char -> a -> a
cons c = (formatChar' c <>)
rjust :: Char -> Int -> a -> a
ljust :: Int -> a -> a
output :: proxy a -> a -> Output a
formatChar' :: Char -> a
formatDec' :: Integral i => i -> a
formatOct' :: Integral i => i -> a
formatHex' :: Integral i => i -> a
formatHexUpper' :: Integral i => i -> a
formatFloat' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
formatSci' :: RealFloat f => Maybe Int -> f -> a
formatSciUpper' :: RealFloat f => Maybe Int -> f -> a
formatG' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
formatGUpper' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
data ArgSpec v = ArgSpec
{ flagSet :: FlagSet
, width :: Maybe Int
, prec :: Maybe Int
, value :: v
}
data Direction
= Leftward
| Rightward
deriving (Show)
data Pad
= Space
| Zero
deriving (Eq, Show)
data Val v = Val
{ valLit :: v
, valWidth :: Maybe Int
, valPrefix :: Maybe (Int, v)
, valSign :: Maybe (Int, v)
, valPad :: Pad
, valDirection :: Direction
} deriving (Show)
valOf x =
Val
{ valLit = x
, valWidth = Nothing
, valPrefix = Nothing
, valSign = Nothing
, valPad = Space
, valDirection = Rightward
}
valSign' = maybe mempty snd . valSign
valPrefix' = maybe mempty snd . valPrefix
setSign x v = v {valSign = Just $ fmap literal x}
setPrefix x v = v {valPrefix = Just $ fmap literal x}
setRightAligned v = v {valDirection = Rightward}
setLeftAligned v = v {valDirection = Leftward}
setWidth n v = v {valWidth = Just n}
setWidth' n v = v {valWidth = n}
setZero v = v {valPad = Zero}
instance Functor ArgSpec where
fmap f a@(ArgSpec {value}) = a {value = f value}
adjust (ArgSpec flags width _ _) =
setWidth' width .
case adjustment flags of
Nothing -> id
Just LeftJustified -> setLeftAligned
Just ZeroPadded -> setZero
helper :: (Num a, Eq a, Printer p) => (a -> p) -> String -> ArgSpec a -> Val p
helper f pref spec = adjustAndSign pref spec $ valOf $ f (abs $ value spec)
adjustAndSign :: (Num n, Printer a, Eq n) => String -> ArgSpec n -> Val a -> Val a
adjustAndSign pref (ArgSpec flags width _ num) =
adj . setWidth' width . sign flags num . prefix pref flags num
where
adj =
case adjustment flags of
Nothing -> id
Just LeftJustified -> setLeftAligned
Just ZeroPadded -> setZero
prefix _ _ 0 = id
prefix s flags _
| prefixed flags = setPrefix (length s, s)
| otherwise = id
sign flags n
| signum n == 1 = setSign (1, "-")
| spaced flags = setSign (1, " ")
| signed flags = setSign (1, "+")
| otherwise = id
formatDec = helper formatDec' ""
formatOct = helper formatOct' "0"
formatHex = helper formatHex' "0x"
formatHexUpper = helper formatHexUpper' "0X"
helper' spec pair
| prefixed (flagSet spec) = helper (snd pair) "" spec
| otherwise = helper (fst pair) "" spec
formatFloat spec = helper' spec (formatFloat' (prec spec))
formatSci spec = helper (formatSci' (prec spec)) "" spec
formatSciUpper spec = helper (formatSciUpper' (prec spec)) "" spec
formatG spec = helper' spec (formatG' (prec spec))
formatGUpper spec = helper' spec (formatGUpper' (prec spec))
formatNat = formatDec . fmap (fromIntegral :: Natural -> Integer)
fOne v@(Val {valWidth = Nothing, ..}) = mconcat [valSign' v, valPrefix' v, valLit]
fOne v@(Val {valWidth = Just n, valDirection = Rightward, ..}) =
if valPad == Zero
then mconcat
[ maybe mempty snd valSign
, maybe mempty snd valPrefix
, rjust '0' (n extra) valLit
]
else rjust ' ' n $ fOne (v {valWidth = Nothing})
where
extra = maybe 0 fst valPrefix + maybe 0 fst valSign
fOne v@(Val {valWidth = Just n, valDirection = Leftward, ..}) =
ljust n $ fOne (v {valWidth = Nothing})
finalize p = foldMap (output p . fOne)
formatStr spec = adjust spec $ valOf $ literal (value spec)
formatChar spec = adjust spec $ valOf $ formatChar' (value spec)
literal x
| '\\' `elem` x =
(`fix` x) $ \f s ->
case readP_to_S lexChar s of
((c, rest):_) -> cons c (f rest)
[] -> mempty
| otherwise = string x
formatShowable spec = adjust spec $ valOf $ literal (show $ value spec)