{-# Language RecordWildCards #-} {-# Language DefaultSignatures #-} {-# Language OverloadedStrings #-} {-# Language NamedFieldPuns #-} {-# Language FlexibleContexts #-} {-# Language FlexibleInstances #-} {-# Language TypeSynonymInstances #-} {-# Language TypeFamilies #-} 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)