packages feed

th-printf-0.8: src/Language/Haskell/Printf/Printers.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoMonomorphismRestriction #-}

module Language.Haskell.Printf.Printers where

import Data.Char
import Data.Maybe (fromMaybe)
import Data.String (IsString, fromString)
import qualified Data.Text as S
import qualified Data.Text.Lazy as L
import Foreign.Ptr
import GHC.Float (FFFormat (..))
import Language.Haskell.Printf.Geometry
import Language.Haskell.PrintfArg
import Math.NumberTheory.Logarithms

import Buf
import NumUtils
import qualified Parser.Types as P

type Printer n buf = PrintfArg n -> Value buf

printfString :: (Buf buf) => Printer String buf
printfString spec =
  Value
    { valArg = case prec spec of
        Nothing -> str <$> spec
        Just c -> str . take c <$> spec
    , valPrefix = Nothing
    , valSign = Nothing
    }

printfStrictText :: (Buf buf) => Printer S.Text buf
printfStrictText spec =
  Value
    { valArg = case prec spec of
        Nothing -> sText <$> spec
        Just c -> sText . S.take c <$> spec
    , valPrefix = Nothing
    , valSign = Nothing
    }

printfLazyText :: (Buf buf) => Printer L.Text buf
printfLazyText spec =
  Value
    { valArg = case prec spec of
        Nothing -> lText <$> spec
        Just c -> lText . L.take (fromIntegral c) <$> spec
    , valPrefix = Nothing
    , valSign = Nothing
    }

printfShow :: (Buf buf, Show a) => Printer a buf
printfShow spec = printfString (fromString . show <$> spec)

printfChar :: (Buf buf) => Printer Char buf
printfChar spec =
  Value
    { valArg = singleton <$> spec
    , valPrefix = Nothing
    , valSign = Nothing
    }

{-# ANN printfPtr ("HLint: ignore Use showHex" :: String) #-}
printfPtr :: (Buf buf) => Printer (Ptr a) buf
printfPtr spec =
  Value
    { valArg =
        PrintfArg
          { width = width spec
          , prec = Nothing
          , flagSet = P.emptyFlagSet{P.prefixed = True}
          , lengthSpec = Nothing
          , fieldSpec = 'p'
          , value = showIntAtBase 16 intToDigit (ptrToWordPtr $ value spec)
          }
    , valPrefix = Just (str "0x")
    , valSign = Nothing
    }

printfDecimal :: (Buf buf, Show n, Integral n) => PrintfArg n -> Value buf
printfDecimal spec =
  Value
    { valArg = padDecimal spec . showIntAtBase 10 intToDigit . abs <$> spec
    , valPrefix = Nothing
    , valSign = sign' spec
    }

fmtUnsigned ::
  (Bounded a, Integral a, Buf buf) =>
  (Integer -> buf) ->
  (PrintfArg a -> Maybe buf) ->
  Printer a buf
fmtUnsigned shower p spec =
  Value
    { valArg = padDecimal spec . shower . clampUnsigned <$> spec
    , valPrefix = p spec
    , valSign = Nothing
    }

printfHex :: (Bounded a, Integral a, Buf buf, IsString buf) => Bool -> Printer a buf
printfHex b = fmtUnsigned showHex (prefix (if b then "0X" else "0x"))
 where
  showHex = showIntAtBase 16 ((if b then toUpper else id) . intToDigit)

printfUnsigned :: (Bounded a, Integral a, Buf buf) => Printer a buf
printfUnsigned = fmtUnsigned (showIntAtBase 10 intToDigit) (const Nothing)

-- printing octal is really annoying.  consider
--
-- printf "%#-8.5x" 1234
--
-- "0x004d2 "
--  ^~~~~~~^ width (8)
--    ^~~~^  precision (5)
--  ^^       prefix (2)
--    ^^     padding (2)
--
-- printf "%#-8.5o" 1234
--
-- "02322   "
--  ^~~~~~~^ width (8)
--  ^~~~^    precision (5)
--  ^        prefix (1)
--  ^        padding (1, same character)
--
-- in octal, when combining prefix and padding, the prefix
-- must eat the first padding char
{-# ANN printfOctal ("HLint: ignore Use showOct" :: String) #-}
printfOctal :: (Buf buf, IsString buf, Bounded n, Integral n) => PrintfArg n -> Value buf
printfOctal spec =
  fmtUnsigned
    (showIntAtBase 8 intToDigit)
    (\y -> if shouldUnpad then Nothing else prefix "0" y)
    spec
 where
  expectedWidth = integerLogBase 8 (max 1 $ clampUnsigned $ value spec) + 1
  shouldUnpad = prefixed spec && fromMaybe 0 (prec spec) > expectedWidth

printfFloating :: (Buf buf, RealFloat n) => Bool -> PrintfArg n -> Value buf
printfFloating upperFlag spec =
  Value
    { valArg = showFloat . abs <$> spec
    , valPrefix = Nothing
    , valSign = sign' spec
    }
 where
  precision = case prec spec of
    Just n -> Just (fromIntegral n)
    Nothing | Just P.ZeroPadded <- adjustment spec -> Just 6
    _ -> Nothing
  showFloat = formatRealFloatAlt FFFixed precision (prefixed spec) upperFlag

printfScientific :: (Buf buf, RealFloat n) => Bool -> PrintfArg n -> Value buf
printfScientific upperFlag spec =
  Value
    { valArg = showSci . abs <$> spec
    , valPrefix = Nothing
    , valSign = sign' spec
    }
 where
  showSci =
    formatRealFloatAlt
      FFExponent
      (fromIntegral <$> prec spec)
      (prefixed spec)
      upperFlag

printfGeneric :: (Buf buf, RealFloat n) => Bool -> PrintfArg n -> Value buf
printfGeneric upperFlag spec =
  Value
    { valArg = showSci . abs <$> spec
    , valPrefix = Nothing
    , valSign = sign' spec
    }
 where
  showSci =
    formatRealFloatAlt
      FFGeneric
      (fromIntegral <$> prec spec)
      (prefixed spec)
      upperFlag

printfFloatHex :: (Buf buf, RealFloat n, IsString buf) => Bool -> PrintfArg n -> Value buf
printfFloatHex upperFlag spec =
  Value
    { valArg = showHexFloat . abs <$> spec
    , valPrefix = Just (if upperFlag then "0X" else "0x")
    , valSign = sign' spec
    }
 where
  showHexFloat =
    formatHexFloat (fromIntegral <$> prec spec) (prefixed spec) upperFlag

clampUnsigned :: (Bounded a, Integral a) => a -> Integer
clampUnsigned x
  | x < 0 = toInteger x + (-2 * toInteger (minBound `asTypeOf` x))
  | otherwise = toInteger x