printf

Lennart Augustsson lennart at augustsson.net
Tue Nov 23 16:37:42 EST 2004


John Goerzen wrote:
> On Tue, Nov 23, 2004 at 09:59:35PM +0100, Lennart Augustsson wrote:
> 
>>John Goerzen wrote:
>>
>>>On 2004-11-23, Lennart Augustsson <lennart at augustsson.net> wrote:
>>>
>>>
>>>>Being an old C programmer I'd like to suggest a library, Text.Printf,
>>>>for C printf() style formatting.
>>>
>>>
>>>Please take a look at MissingH.Printf:
>>>
>>>http://gopher.quux.org:70/devel/missingh/html/MissingH.Printf.html
>>>
>>
>>My printf is a little different in that it doesn't require a union
>>type for the arguments, nor do they have to be in a list.  But
>>those are details.
> 
> 
> Where can I find yours?
> 
> 
>>	-- Lennart
>>
>>
> 
> 

-------------- next part --------------
-----------------------------------------------------------------------------
-- |
-- Module      :  Text.Printf
-- Copyright   :  (c) Lennart Augustsson, 2004
-- License     :  No license, do whatever you like
-- 
-- Maintainer  :  lennart at augustsson.net
-- Stability   :  provisional
-- Portability :  portable
--
-- $Id$
--
--
-- A C printf like formatter.
-- Conversion specs:
--	-	left adjust
--	num	field width
--      *       as num, but taken from argument list
--	.	separates width from precision
-- Formatting characters:
-- 	c	Char, Int, Integer
--	d	Char, Int, Integer
--	o	Char, Int, Integer
--	x	Char, Int, Integer
--	u	Char, Int, Integer
--	f	Float, Double
--	g	Float, Double
--	e	Float, Double
--	s	String
--
-- The printf function takes a formatting string followed by a variable
-- number of arguments.  It returns a String or an IO a.
--
-----------------------------------------------------------------------------

module Printf(printf) where
import Array
import Char
import Numeric(showEFloat, showFFloat, showGFloat)

-------------------

-- | Format a variable number of arguments with the C style formatting string.
-- The return value is a String or (IO a).
printf :: (PrintfType r) => String -> r
printf fmt = spr fmt []

class PrintfType t where
    spr :: 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 fmt args = map fromChar (uprintf fmt (reverse args))

instance PrintfType (IO a) where
    spr fmt args = do
	putStr (uprintf fmt (reverse args))
	return undefined

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmt args = \ a -> spr fmt (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 s = UString (map toChar s)

instance PrintfArg Int where
    toUPrintf i = UInt i

instance PrintfArg Integer where
    toUPrintf i = UInteger i

instance PrintfArg Float where
    toUPrintf f = UFloat f

instance PrintfArg Double where
    toUPrintf d = UDouble d

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 | UInt Int | UInteger 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, cs', us') = getSpecs 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
        in
	case cs' of
	[]     -> fmterr
	c:cs'' ->
	    case us' of
	    []     -> argerr
	    u:us'' ->
		(case c of
		'c' -> adjust ("", [toEnum (toint u)])
		'd' -> adjust (fmti u)
		'x' -> adjust ("", fmtu 16 u)
		'o' -> adjust ("", fmtu 8  u)
		'u' -> adjust ("", fmtu 10 u)
		'e' -> adjust (dfmt' c prec u)
		'f' -> adjust (dfmt' c prec u)
		'g' -> adjust (dfmt' c prec u)
		's' -> adjust ("", tostr u)
		c   -> perror ("bad formatting char " ++ [c])
		 ) ++ uprintf cs'' us''

fmti (UInt i)     = if i < 0 then
			if i == -i then fmti (UInteger (toInteger i)) else ("-", itos (-i))
		    else
			("", itos i)
fmti (UInteger i) = if i < 0 then ("-", itos (-i)) else ("", itos i)
fmti (UChar c)    = fmti (UInt (fromEnum c))
fmti u		  = baderr

fmtu b (UInt i)     = if i < 0 then
			  if i == -i then itosb b (maxi - toInteger (i+1) - 1) else itosb b (maxi - toInteger (-i))
		      else
			  itosb b (toInteger i)
fmtu b (UInteger i) = itosb b i
fmtu b (UChar c)    = itosb b (toInteger (fromEnum c))
fmtu b u            = baderr

maxi :: Integer
maxi = (toInteger (maxBound::Int) + 1) * 2

toint (UInt i)     = i
toint (UInteger i) = toInt i
toint (UChar c)    = fromEnum c
toint u		   = baderr

tostr (UString s) = s
tostr u		  = baderr

itos n = 
	if n < 10 then 
	    [toEnum (fromEnum '0' + toInt n)]
	else
	    let (q, r) = quotRem n 10 in
	    itos q ++ [toEnum (fromEnum '0' + toInt r)]

chars = array (0,15) (zipWith (,) [0..] "0123456789abcdef")
itosb :: Integer -> Integer -> String
itosb b n = 
	if n < b then 
	    [chars!n]
	else
	    let (q, r) = quotRem n b in
	    itosb b q ++ [chars!r]

stoi :: Int -> String -> (Int, String)
stoi a (c:cs) | isDigit c = stoi (a*10 + fromEnum c - fromEnum '0') cs
stoi a cs                 = (a, cs)

getSpecs :: Bool -> Bool -> String -> [UPrintf] -> (Int, Int, Bool, Bool, String, [UPrintf])
getSpecs l z ('-':cs) us = getSpecs True z cs us
getSpecs l z ('0':cs) us = getSpecs l True cs us
getSpecs l z ('*':cs) us = 
        case us of
        [] -> argerr
        nu : us' ->
	    let n = toint nu
		(p, cs'', us'') =
		    case cs of
                    '.':'*':r -> case us' of { [] -> argerr; pu:us'' -> (toint pu, r, us'') }
		    '.':r     -> let (n, cs') = stoi 0 r in (n, cs', us')
		    _         -> (-1, cs, us')
	    in  (n, p, l, z, cs'', us'')
getSpecs l z ('.':cs) us =
	let (p, cs') = stoi 0 cs
	in  (0, p, l, z, cs', us)
getSpecs l z cs@(c:_) us | isDigit c =
	let (n, cs') = stoi 0 cs
	    (p, cs'') = case cs' of
			'.':r -> stoi 0 r
			_     -> (-1, cs')
	in  (n, p, l, z, cs'', us)
getSpecs l z cs       us = (0, -1, l, z, cs, us)

dfmt' c p (UDouble d) = dfmt c p d
dfmt' c p (UFloat f)  = dfmt c p f
dfmt' c p u           = baderr

dfmt c p d = 
	case (case c of 'e' -> showEFloat; 'f' -> showFFloat; 'g' -> showGFloat) 
               (if p < 0 then Nothing else Just p) d "" of
	'-':cs -> ("-", cs)
	cs     -> ("" , cs)

perror s = error ("Printf.printf: "++s)
fmterr = perror "formatting string ended prematurely"
argerr = perror "argument list ended prematurely"
baderr = perror "bad argument"

toInt :: (Integral a) => a -> Int
toInt x = fromInteger (toInteger x)


More information about the Libraries mailing list