th-printf-0.8: src/Language/Haskell/Printf.hs
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{- | "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)
WAS "4294967295"
NOW Not in scope: type constructor or class `Int32'
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.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"
}