th-printf-0.7: src/Language/Haskell/Printf.hs
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
-- | "Text.Printf" is a useful module, but due to the typeclass hacks it uses, it can
-- be hard to tell if the format string you wrote is well-formed or not.
-- This package provides a mechanism to create formatting functions at compile time.
--
-- Note that, to maintain consistency with other printf implementations, negative ints
-- that are printed as unsigned will \"underflow\". (Text.Printf does this too.)
--
-- >>> [s|%u|] (-1 :: Int32)
-- "4294967295"
--
-- Thus, any time you want to print a number using the unsigned, octal, or hex specifiers,
-- your input must be an instance of "Bounded".
module Language.Haskell.Printf
( s
, t
, p
, hp
)
where
import Control.Applicative ( pure )
import Control.Monad.IO.Class
import Language.Haskell.Printf.Lib
import Language.Haskell.TH.Lib
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import System.IO ( hPutStr )
-- | @
-- ['s'|Hello, %s! (%d people greeted)|] :: ... -> 'String'
-- @
--
-- This formatter follows the guidelines listed
-- <http://www.cplusplus.com/reference/cstdio/printf/ here>, except for
-- @%n@ (store number of printed characters) for obvious
-- reasons.
--
-- @
-- %c :: 'Char'
-- %s :: 'String'
-- %q :: 'Data.Text.Lazy.Text' -- lazy text
-- %Q :: 'Data.Text.Text' -- strict text
--
-- -- datatypes with Show instances
-- %? :: 'Show' a => a
--
-- -- signed integer types
-- %d, %i :: 'Integral' i => i
--
-- -- unsigned integer types
-- %u :: ('Bounded' i, 'Integral' i) => i
-- %o :: ('Bounded' i, 'Integral' i) => i
-- %x, %X :: ('Bounded' i, 'Integral' i) => i
--
-- -- floats
-- %a, %A :: 'RealFloat' f => f
-- %e, %E :: 'RealFloat' f => f
-- %f, %F :: 'RealFloat' f => f
-- %g, %G :: 'RealFloat' f => f
--
-- %p :: 'Foreign.Ptr.Ptr' a
-- @
s :: QuasiQuoter
s = quoter $ \s' -> do
(lhss, rhs) <- toSplices s' OutputString
return $ LamE lhss rhs
-- | Behaves identically to 's', but produces lazy 'Data.Text.Lazy.Text'.
t :: QuasiQuoter
t = quoter $ \s' -> do
(lhss, rhs) <- toSplices s' OutputText
return $ LamE lhss rhs
-- | Like 's', but prints the resulting string to @stdout@.
--
-- @
-- [p|Hello, %s! (%d people greeted)|] :: 'MonadIO' m => ... -> m ()
-- @
p :: QuasiQuoter
p = quoter $ \s' -> do
(lhss, rhs) <- toSplices s' OutputString
lamE (map pure lhss) [|liftIO (putStr $(pure rhs))|]
-- | Like 'p', but takes as its first argument the 'System.IO.Handle' to print to.
--
-- @
-- [hp|Hello, %s! (%d people greeted)|] :: 'MonadIO' m => 'System.IO.Handle' -> ... -> m ()
-- @
hp :: QuasiQuoter
hp = quoter $ \s' -> do
(lhss, rhs) <- toSplices s' OutputString
h <- newName "h"
lamE (varP h : map pure lhss) [|liftIO (hPutStr $(varE h) $(pure rhs))|]
quoter :: (String -> ExpQ) -> QuasiQuoter
quoter e = QuasiQuoter
{ quoteExp = e
, quotePat = error "this quoter cannot be used in a pattern context"
, quoteType = error "this quoter cannot be used in a type context"
, quoteDec = error "this quoter cannot be used in a declaration context"
}