content: {-# LANGUAGE Safe #-}
  {-# LANGUAGE CPP #-}
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  Text.Printf
  -- Copyright   :  (c) Lennart Augustsson, 2004-2008
  -- License     :  BSD-style (see the file libraries/base/LICENSE)
  -- 
  -- Maintainer  :  lennart@augustsson.net
  -- Stability   :  provisional
  -- Portability :  portable
  --
  -- A C printf like formatter.
  --
  -----------------------------------------------------------------------------
  
  {-# Language CPP #-}
  
  module Text.Printf(
     printf, hPrintf,
     PrintfType, HPrintfType, PrintfArg, IsChar
  ) where
  
  import Prelude
  import Data.Char
  import Data.Int
  import Data.Word
  import Numeric(showEFloat, showFFloat, showGFloat)
  import System.IO
  
  -------------------
  
  -- | Format a variable number of arguments with the C-style formatting string.
  -- The return value is either 'String' or @('IO' a)@.
  --
  -- The format string consists of ordinary characters and /conversion
  -- specifications/, which specify how to format one of the arguments
  -- to printf in the output string.  A conversion specification begins with the
  -- character @%@, followed by one or more of the following flags:
  --
  -- >    -      left adjust (default is right adjust)
  -- >    +      always use a sign (+ or -) for signed conversions
  -- >    0      pad with zeroes rather than spaces
  --
  -- followed optionally by a field width:
  -- 
  -- >    num    field width
  -- >    *      as num, but taken from argument list
  --
  -- followed optionally by a precision:
  --
  -- >    .num   precision (number of decimal places)
  --
  -- and finally, a format character:
  --
  -- >    c      character               Char, Int, Integer, ...
  -- >    d      decimal                 Char, Int, Integer, ...
  -- >    o      octal                   Char, Int, Integer, ...
  -- >    x      hexadecimal             Char, Int, Integer, ...
  -- >    X      hexadecimal             Char, Int, Integer, ...
  -- >    u      unsigned decimal        Char, Int, Integer, ...
  -- >    f      floating point          Float, Double
  -- >    g      general format float    Float, Double
  -- >    G      general format float    Float, Double
  -- >    e      exponent format float   Float, Double
  -- >    E      exponent format float   Float, Double
  -- >    s      string                  String
  --
  -- Mismatch between the argument types and the format string will cause
  -- an exception to be thrown at runtime.
  --
  -- Examples:
  --
  -- >   > printf "%d\n" (23::Int)
  -- >   23
  -- >   > printf "%s %s\n" "Hello" "World"
  -- >   Hello World
  -- >   > printf "%.2f\n" pi
  -- >   3.14
  --
  printf :: (PrintfType r) => String -> r
  printf fmts = spr fmts []
  
  -- | Similar to 'printf', except that output is via the specified
  -- 'Handle'.  The return type is restricted to @('IO' a)@.
  hPrintf :: (HPrintfType r) => Handle -> String -> r
  hPrintf hdl fmts = hspr hdl fmts []
  
  -- |The 'PrintfType' class provides the variable argument magic for
  -- 'printf'.  Its implementation is intentionally not visible from
  -- this module. If you attempt to pass an argument of a type which
  -- is not an instance of this class to 'printf' or 'hPrintf', then
  -- the compiler will report it as a missing instance of 'PrintfArg'.
  class PrintfType t where
      spr :: String -> [UPrintf] -> t
  
  -- | The 'HPrintfType' class provides the variable argument magic for
  -- 'hPrintf'.  Its implementation is intentionally not visible from
  -- this module.
  class HPrintfType t where
      hspr :: Handle -> String -> [UPrintf] -> t
  
  {- not allowed in Haskell 98
  instance PrintfType String where
      spr fmt args = uprintf fmt (reverse args)
  -}
  instance (IsChar c) => PrintfType [c] where
      spr fmts args = map fromChar (uprintf fmts (reverse args))
  
  instance PrintfType (IO a) where
      spr fmts args = do
  	putStr (uprintf fmts (reverse args))
  	return (error "PrintfType (IO a): result should not be used.")
  
  instance HPrintfType (IO a) where
      hspr hdl fmts args = do
  	hPutStr hdl (uprintf fmts (reverse args))
  	return (error "HPrintfType (IO a): result should not be used.")
  
  instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
      spr fmts args = \ a -> spr fmts (toUPrintf a : args)
  
  instance (PrintfArg a, HPrintfType r) => HPrintfType (a -> r) where
      hspr hdl fmts args = \ a -> hspr hdl fmts (toUPrintf a : args)
  
  class PrintfArg a where
      toUPrintf :: a -> UPrintf
  
  instance PrintfArg Char where
      toUPrintf c = UChar c
  
  {- not allowed in Haskell 98
  instance PrintfArg String where
      toUPrintf s = UString s
  -}
  instance (IsChar c) => PrintfArg [c] where
      toUPrintf = UString . map toChar
  
  instance PrintfArg Int where
      toUPrintf = uInteger
  
  instance PrintfArg Int8 where
      toUPrintf = uInteger
  
  instance PrintfArg Int16 where
      toUPrintf = uInteger
  
  instance PrintfArg Int32 where
      toUPrintf = uInteger
  
  instance PrintfArg Int64 where
      toUPrintf = uInteger
  
  #ifndef __NHC__
  instance PrintfArg Word where
      toUPrintf = uInteger
  #endif
  
  instance PrintfArg Word8 where
      toUPrintf = uInteger
  
  instance PrintfArg Word16 where
      toUPrintf = uInteger
  
  instance PrintfArg Word32 where
      toUPrintf = uInteger
  
  instance PrintfArg Word64 where
      toUPrintf = uInteger
  
  instance PrintfArg Integer where
      toUPrintf = UInteger 0
  
  instance PrintfArg Float where
      toUPrintf = UFloat
  
  instance PrintfArg Double where
      toUPrintf = UDouble
  
  uInteger :: (Integral a, Bounded a) => a -> UPrintf
  uInteger x = UInteger (toInteger $ minBound `asTypeOf` x) (toInteger x)
  
  class IsChar c where
      toChar :: c -> Char
      fromChar :: Char -> c
  
  instance IsChar Char where
      toChar c = c
      fromChar c = c
  
  -------------------
  
  data UPrintf = UChar Char | UString String | UInteger Integer Integer | UFloat Float | UDouble Double
  
  uprintf :: String -> [UPrintf] -> String
  uprintf ""       []       = ""
  uprintf ""       (_:_)    = fmterr
  uprintf ('%':'%':cs) us   = '%':uprintf cs us
  uprintf ('%':_)  []       = argerr
  uprintf ('%':cs) us@(_:_) = fmt cs us
  uprintf (c:cs)   us       = c:uprintf cs us
  
  fmt :: String -> [UPrintf] -> String
  fmt cs us =
  	let (width, prec, ladj, zero, plus, cs', us') = getSpecs False False False cs us
  	    adjust (pre, str) = 
  		let lstr = length str
  		    lpre = length pre
  		    fill = if lstr+lpre < width then take (width-(lstr+lpre)) (repeat (if zero then '0' else ' ')) else ""
  		in  if ladj then pre ++ str ++ fill else if zero then pre ++ fill ++ str else fill ++ pre ++ str
              adjust' ("", str) | plus = adjust ("+", str)
              adjust' ps = adjust ps
          in
  	case cs' of
  	[]     -> fmterr
  	c:cs'' ->
  	    case us' of
  	    []     -> argerr
  	    u:us'' ->
  		(case c of
  		'c' -> adjust  ("", [toEnum (toint u)])
  		'd' -> adjust' (fmti prec u)
  		'i' -> adjust' (fmti prec u)
  		'x' -> adjust  ("", fmtu 16 prec u)
  		'X' -> adjust  ("", map toUpper $ fmtu 16 prec u)
  		'o' -> adjust  ("", fmtu 8  prec u)
  		'u' -> adjust  ("", fmtu 10 prec u)
  		'e' -> adjust' (dfmt' c prec u)
  		'E' -> adjust' (dfmt' c prec u)
  		'f' -> adjust' (dfmt' c prec u)
  		'g' -> adjust' (dfmt' c prec u)
  		'G' -> adjust' (dfmt' c prec u)
  		's' -> adjust  ("", tostr prec u)
  		_   -> perror ("bad formatting char " ++ [c])
  		 ) ++ uprintf cs'' us''
  
  fmti :: Int -> UPrintf -> (String, String)
  fmti prec (UInteger _ i) = if i < 0 then ("-", integral_prec prec (show (-i))) else ("", integral_prec prec (show i))
  fmti _ (UChar c)         = fmti 0 (uInteger (fromEnum c))
  fmti _ _                 = baderr
  
  fmtu :: Integer -> Int -> UPrintf -> String
  fmtu b prec (UInteger l i) = integral_prec prec (itosb b (if i < 0 then -2*l + i else i))
  fmtu b _    (UChar c)      = itosb b (toInteger (fromEnum c))
  fmtu _ _ _                 = baderr
  
  integral_prec :: Int -> String -> String
  integral_prec prec integral = (replicate (prec - (length integral)) '0') ++ integral
  
  toint :: UPrintf -> Int
  toint (UInteger _ i) = fromInteger i
  toint (UChar c)      = fromEnum c
  toint _		     = baderr
  
  tostr :: Int -> UPrintf -> String
  tostr n (UString s) = if n >= 0 then take n s else s
  tostr _ _		  = baderr
  
  itosb :: Integer -> Integer -> String
  itosb b n = 
  	if n < b then 
  	    [intToDigit $ fromInteger n]
  	else
  	    let (q, r) = quotRem n b in
  	    itosb b q ++ [intToDigit $ fromInteger r]
  
  stoi :: Int -> String -> (Int, String)
  stoi a (c:cs) | isDigit c = stoi (a*10 + digitToInt c) cs
  stoi a cs                 = (a, cs)
  
  getSpecs :: Bool -> Bool -> Bool -> String -> [UPrintf] -> (Int, Int, Bool, Bool, Bool, String, [UPrintf])
  getSpecs _ z s ('-':cs) us = getSpecs True z s cs us
  getSpecs l z _ ('+':cs) us = getSpecs l z True cs us
  getSpecs l _ s ('0':cs) us = getSpecs l True s cs us
  getSpecs l z s ('*':cs) us =
  	let (us', n) = getStar us
  	    ((p, cs''), us'') =
  		    case cs of
                      '.':'*':r -> let (us''', p') = getStar us'
  		    	      	 in  ((p', r), us''')
  		    '.':r     -> (stoi 0 r, us')
  		    _         -> ((-1, cs), us')
  	in  (n, p, l, z, s, cs'', us'')
  getSpecs l z s ('.':cs) us =
  	let ((p, cs'), us') = 
  	        case cs of
  		'*':cs'' -> let (us'', p') = getStar us in ((p', cs''), us'')
                  _ ->        (stoi 0 cs, us)
  	in  (0, p, l, z, s, cs', us')
  getSpecs l z s cs@(c:_) us | isDigit c =
  	let (n, cs') = stoi 0 cs
  	    ((p, cs''), us') = case cs' of
  	    	 	       '.':'*':r -> let (us'', p') = getStar us in ((p', r), us'')
  		               '.':r -> (stoi 0 r, us)
  			       _     -> ((-1, cs'), us)
  	in  (n, p, l, z, s, cs'', us')
  getSpecs l z s cs       us = (0, -1, l, z, s, cs, us)
  
  getStar :: [UPrintf] -> ([UPrintf], Int)
  getStar us =
      case us of
      [] -> argerr
      nu : us' -> (us', toint nu)
  
  
  dfmt' :: Char -> Int -> UPrintf -> (String, String)
  dfmt' c p (UDouble d) = dfmt c p d
  dfmt' c p (UFloat f)  = dfmt c p f
  dfmt' _ _ _           = baderr
  
  dfmt :: (RealFloat a) => Char -> Int -> a -> (String, String)
  dfmt c p d =
  	case (if isUpper c then map toUpper else id) $
               (case toLower c of
                    'e' -> showEFloat
                    'f' -> showFFloat
                    'g' -> showGFloat
                    _   -> error "Printf.dfmt: impossible"
               )
                 (if p < 0 then Nothing else Just p) d "" of
  	'-':cs -> ("-", cs)
  	cs     -> ("" , cs)
  
  perror :: String -> a
  perror s = error ("Printf.printf: "++s)
  fmterr, argerr, baderr :: a
  fmterr = perror "formatting string ended prematurely"
  argerr = perror "argument list ended prematurely"
  baderr = perror "bad argument"
  
  