diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,21 @@
+## 0.7.0 (2019-09-12)
+
+* New `%Q` and `%q` format specifiers accept strict and lazy Text as input
+  respectively. Otherwise they function identically to the `%s` specifier.
+* th-printf can now produce lazy Text as well as String, and the improved
+  internal representation of format strings should slightly increase performance.
+  * Directly producing Text should now be significantly faster than using the 
+    string formatter and `pack`ing the result, especially with Text format arguments.
+* Dropped support for GHC < 8.
+
+## 0.6.0 (2018-08-18)
+
+Backported new backpack-based code to pre GHC-8.4 versions.
+
+* Rename of public modules
+* Parser rewrite
+* th-printf now prints a warning when given an erroneous format string
+* Several printf behaviors have been updated to comply with spec:
+  * `x`, `u`, etc. specifiers now only apply to positive integers
+  * Length specifiers are allowed
+* Generated testsuite covers more cases
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import           Distribution.Simple
+main = defaultMain
diff --git a/include/compat-defs.h b/include/compat-defs.h
new file mode 100644
--- /dev/null
+++ b/include/compat-defs.h
@@ -0,0 +1,5 @@
+#if __GLASGOW_HASKELL__ >= 840
+#define MONOID_HEAD Monoid a
+#else
+#define MONOID_HEAD (Semigroup a, Monoid a)
+#endif
diff --git a/parser/Parser.hs b/parser/Parser.hs
--- a/parser/Parser.hs
+++ b/parser/Parser.hs
@@ -1,4 +1,5 @@
 {-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE FlexibleContexts #-}
@@ -6,139 +7,147 @@
 
 module Parser where
 
-import Control.Applicative
-import Control.Monad.Fix
-import Control.Monad.RWS
-import Data.Char
-import Data.CharSet hiding (map)
-import Data.Maybe
-import qualified Data.Set as S
-import Lens.Micro.Platform
-import Parser.Types
-import Text.Parsec hiding (many)
-import Text.ParserCombinators.ReadP (readP_to_S)
-import Text.Read.Lex (lexChar)
+import           Control.Applicative
+import           Control.Monad.Fix
+import           Control.Monad.RWS
+import           Data.Char
+import           Data.CharSet            hiding ( map )
+import           Data.Maybe
+import qualified Data.Set                      as S
+import           Lens.Micro.Platform
+import           Parser.Types
+import           Text.Parsec             hiding ( many )
+import           Text.ParserCombinators.ReadP   ( readP_to_S )
+import           Text.Read.Lex                  ( lexChar )
 
 type Warning = String
 
 parseStr :: String -> Either ParseError ([Atom], [[Warning]])
 parseStr = fmap (unzip . map normalizeAndWarn) . parse printfStr "" . lexChars
-  where
-    lexChars x =
-        (`fix` x) $ \f s ->
-            if Prelude.null s
-                then []
-                else case readP_to_S lexChar s of
-                         ((c, rest):_) -> c : f rest
-                         [] -> error "malformed input"
+ where
+  lexChars x = (`fix` x) $ \f s -> if Prelude.null s
+    then []
+    else case readP_to_S lexChar s of
+      ((c, rest) : _) -> c : f rest
+      []              -> error "malformed input"
 
 normalizeAndWarn :: Atom -> (Atom, [Warning])
-normalizeAndWarn s@Str {} = (s, [])
+normalizeAndWarn s@Str{} = (s, [])
 normalizeAndWarn (Arg f) = (Arg a, b)
-  where
-    (_, a, b) = runRWS (warnLength f >> go (spec f)) () f
-    go c
-        | c `elem` "aAeEfFgGxXo" = return ()
-    go c
-        | c `elem` "cs?" = warnSign >> warnPrefix >> warnZero >> warnSpace
-    go c
-        | c `elem` "diu" = warnPrefix
-    go 'p' = warnSign >> warnPrefix >> warnZero
-    go _ = undefined
-    warnFlag ::
-           (Eq a, MonadWriter [String] m, MonadState FormatArg m)
-        => Lens' FlagSet a
-        -> a
-        -> a
-        -> Char
-        -> m ()
-    warnFlag lens' bad good flagName = do
-        oldVal <- use (flags_ . lens')
-        when (oldVal == bad) $ do
-            c <- use spec_
-            flags_ . lens' .= good
-            tell
-                ["`" ++ [flagName] ++ "` flag has no effect on `" ++ [c] ++ "` specifier"]
-    warnSign = warnFlag signed_ True False '+'
-    warnPrefix = warnFlag prefixed_ True False '#'
-    warnSpace = warnFlag spaced_ True False ' '
-    warnZero = warnFlag adjustment_ (Just ZeroPadded) Nothing '0'
-    phonyLengthSpec =
-        S.fromList $ [(x, y) | x <- "diuoxX", y <- ["L"]] ++
-        [(x, y) | x <- "fFeEgGaA", y <- ["hh", "h", "l", "ll", "j", "z", "t"]] ++
-        [(x, y) | x <- "cs", y <- ["hh", "h", "ll", "j", "z", "t", "L"]] ++
-        map ((,) 'p') ["hh", "h", "l", "ll", "j", "z", "t", "L"]
-    warnLength FormatArg {spec, lengthSpec = Just l}
-        | (spec, show l) `S.member` phonyLengthSpec =
-            tell
-                [ "`" ++ show l ++ "` length modifier has no effect when combined with `" ++
-                  [spec] ++
-                  "` specifier"
-                ]
-    warnLength _ = return ()
+ where
+  (_, a, b) = runRWS (warnLength f >> go (spec f)) () f
+  go c | c `elem` "aAeEfFgGxXo" = return ()
+  go c | c `elem` "csqQ?" = warnSign >> warnPrefix >> warnZero >> warnSpace
+  go c | c `elem` "diu"         = warnPrefix
+  go 'p'                        = warnSign >> warnPrefix >> warnZero
+  go _                          = undefined
+  warnFlag
+    :: (Eq a, MonadWriter [String] m, MonadState FormatArg m)
+    => Lens' FlagSet a
+    -> a
+    -> a
+    -> Char
+    -> m ()
+  warnFlag lens' bad good flagName = do
+    oldVal <- use (flags_ . lens')
+    when (oldVal == bad) $ do
+      c <- use spec_
+      flags_ . lens' .= good
+      tell
+        [ "`"
+          ++ [flagName]
+          ++ "` flag has no effect on `"
+          ++ [c]
+          ++ "` specifier"
+        ]
+  warnSign   = warnFlag signed_ True False '+'
+  warnPrefix = warnFlag prefixed_ True False '#'
+  warnSpace  = warnFlag spaced_ True False ' '
+  warnZero   = warnFlag adjustment_ (Just ZeroPadded) Nothing '0'
+  phonyLengthSpec =
+    S.fromList
+      $  [ (x, y) | x <- "diuoxX", y <- ["L"] ]
+      ++ [ (x, y)
+         | x <- "fFeEgGaA"
+         , y <- ["hh", "h", "l", "ll", "j", "z", "t"]
+         ]
+      ++ [ (x, y) | x <- "csqQ", y <- ["hh", "h", "ll", "j", "z", "t", "L"] ]
+      ++ map ('p', ) ["hh", "h", "l", "ll", "j", "z", "t", "L"]
+  warnLength FormatArg { spec, lengthSpec = Just l }
+    | (spec, show l) `S.member` phonyLengthSpec = tell
+      [ "`"
+        ++ show l
+        ++ "` length modifier has no effect when combined with `"
+        ++ [spec]
+        ++ "` specifier"
+      ]
+  warnLength _ = return ()
 
 flagSet :: CharSet
 flagSet = fromList "-+ #0"
 
 specSet :: CharSet
-specSet = fromList "diuoxXfFeEaAgGpcs?"
+specSet = fromList "diuoxXfFeEaAgGpcsQq?"
 
 lengthSpecifiers :: [(String, LengthSpecifier)]
 lengthSpecifiers =
-    [ ("hh", DoubleH)
-    , ("h", H)
-    , ("ll", DoubleL)
-    , ("l", L)
-    , ("j", J)
-    , ("z", Z)
-    , ("t", T)
-    , ("L", BigL)
-    ]
+  [ ("hh", DoubleH)
+  , ("h" , H)
+  , ("ll", DoubleL)
+  , ("l" , L)
+  , ("j" , J)
+  , ("z" , Z)
+  , ("t" , T)
+  , ("L" , BigL)
+  ]
 
 oneOfSet :: Stream s m Char => CharSet -> ParsecT s u m Char
 oneOfSet s = satisfy (`member` s)
 
 printfStr :: Stream s m Char => ParsecT s u m [Atom]
 printfStr = do
-    atoms <-
-        many $
-        choice [Str "%" <$ try (string "%%"), Arg <$> fmtArg, Str . return <$> noneOf "%"]
-    return $ go atoms
-  where
-    go (Str s:Str s1:as) = go (Str (s ++ s1) : as)
-    go (a:as) = a : go as
-    go [] = []
+  atoms <- many $ choice
+    [Str "%" <$ try (string "%%"), Arg <$> fmtArg, Str . return <$> noneOf "%"]
+  return $ go atoms
+ where
+  go (Str s : Str s1 : as) = go (Str (s ++ s1) : as)
+  go (a              : as) = a : go as
+  go []                    = []
 
 fmtArg :: Stream s m Char => ParsecT s u m FormatArg
 fmtArg = do
-    char '%'
-    flags <-
-        do fs <-
-               many $ do
-                   c <- oneOfSet flagSet <?> "flag"
-                   pure $
-                       case c of
-                           '-' -> FlagLJust
-                           '+' -> FlagSigned
-                           ' ' -> FlagSpaced
-                           '#' -> FlagPrefixed
-                           '0' -> FlagZeroPadded
-                           _ -> error "???"
-           let flagSet' = S.fromList fs
-           if S.size flagSet' < length fs
-               then fail "Duplicate flags specified"
-               else pure $ toFlagSet flagSet'
-    width <- optionMaybe (choice [Given <$> nat, Need <$ char '*']) <?> "width"
-    precision <-
-        optionMaybe
-            (do char '.'
-                optionMaybe $ choice [Given <$> nat, Need <$ char '*']) <?>
-        "precision"
-    lengthSpec <-
-        optionMaybe $ choice $ Prelude.map (\(a, b) -> b <$ string a) lengthSpecifiers
-    spec <- oneOfSet specSet <?> "valid specifier"
-    pure $ FormatArg flags width (fromMaybe (Given 0) <$> precision) spec lengthSpec
-  where
-    nat = do
-        c <- many1 $ satisfy isDigit
-        return (read c :: Integer)
+  char '%'
+  flags <- do
+    fs <- many $ do
+      c <- oneOfSet flagSet <?> "flag"
+      pure $ case c of
+        '-' -> FlagLJust
+        '+' -> FlagSigned
+        ' ' -> FlagSpaced
+        '#' -> FlagPrefixed
+        '0' -> FlagZeroPadded
+        _   -> error "???"
+    let flagSet' = S.fromList fs
+    if S.size flagSet' < length fs
+      then fail "Duplicate flags specified"
+      else pure $ toFlagSet flagSet'
+  width <- optionMaybe (choice [Given <$> nat, Need <$ char '*']) <?> "width"
+  precision <-
+    optionMaybe
+        (do
+          char '.'
+          optionMaybe $ choice [Given <$> nat, Need <$ char '*']
+        )
+      <?> "precision"
+  lengthSpec <- optionMaybe $ choice $ Prelude.map (\(a, b) -> b <$ string a)
+                                                   lengthSpecifiers
+  spec <- oneOfSet specSet <?> "valid specifier"
+  pure $ FormatArg flags
+                   width
+                   (fromMaybe (Given 0) <$> precision)
+                   spec
+                   lengthSpec
+ where
+  nat = do
+    c <- many1 $ satisfy isDigit
+    return (read c :: Integer)
diff --git a/parser/Parser/Types.hs b/parser/Parser/Types.hs
--- a/parser/Parser/Types.hs
+++ b/parser/Parser/Types.hs
@@ -3,12 +3,16 @@
 
 module Parser.Types where
 
-import Data.Foldable (elem, notElem)
-import qualified Data.Set as S
-import Data.Set (Set)
-import Language.Haskell.TH.Lift
-import Lens.Micro.Platform
-import Prelude hiding (elem, notElem)
+import           Data.Foldable                  ( elem
+                                                , notElem
+                                                )
+import qualified Data.Set                      as S
+import           Data.Set                       ( Set )
+import           Language.Haskell.TH.Lift
+import           Lens.Micro.Platform
+import           Prelude                 hiding ( elem
+                                                , notElem
+                                                )
 
 data Atom
     = Arg FormatArg
@@ -27,14 +31,14 @@
     deriving (Eq)
 
 instance Show LengthSpecifier where
-    show DoubleH = "hh"
-    show H = "h"
-    show DoubleL = "ll"
-    show BigL = "L"
-    show L = "l"
-    show J = "j"
-    show Z = "z"
-    show T = "t"
+  show DoubleH = "hh"
+  show H       = "h"
+  show DoubleL = "ll"
+  show BigL    = "L"
+  show L       = "l"
+  show J       = "j"
+  show Z       = "z"
+  show T       = "t"
 
 data Flag
     = FlagLJust
@@ -79,18 +83,15 @@
 
 toFlagSet :: Set Flag -> FlagSet
 toFlagSet fs = set'
-  where
-    adjustment
-        | FlagLJust `S.member` fs = Just LeftJustified
-        | FlagZeroPadded `S.member` fs = Just ZeroPadded
-        | otherwise = Nothing
-    set' =
-        FlagSet
-            { signed = FlagSigned `elem` fs
-            , prefixed = FlagPrefixed `elem` fs
-            , adjustment
-            , spaced = FlagSpaced `elem` fs && FlagSigned `notElem` fs
-            }
+ where
+  adjustment | FlagLJust `S.member` fs      = Just LeftJustified
+             | FlagZeroPadded `S.member` fs = Just ZeroPadded
+             | otherwise                    = Nothing
+  set' = FlagSet { signed     = FlagSigned `elem` fs
+                 , prefixed   = FlagPrefixed `elem` fs
+                 , adjustment
+                 , spaced     = FlagSpaced `elem` fs && FlagSigned `notElem` fs
+                 }
 
 makeLensesFor
     [ ("adjustment", "adjustment_")
diff --git a/src/Buildable.hs b/src/Buildable.hs
new file mode 100644
--- /dev/null
+++ b/src/Buildable.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module Buildable where
+
+import           Data.String
+import qualified Data.DList                    as D
+import           Data.Char                      ( intToDigit )
+import           Data.Text.Lazy                 ( Text )
+import qualified Data.Text.Lazy.Builder        as T
+import qualified Data.Text.Lazy.Builder.Int    as T
+import           Data.Semigroup                 ( Semigroup(..) )
+import qualified Data.Text.Lazy                as L
+import qualified Data.Text                     as S
+
+newtype Sized a = Sized { unSized :: (a, Int) } deriving (Show, Ord, Eq)
+
+type SizedStr = Sized (D.DList Char)
+type SizedBuilder = Sized T.Builder
+
+instance IsString a => IsString (Sized a) where
+  fromString s = Sized (fromString s, length s)
+
+instance Semigroup a => Semigroup (Sized a) where
+  Sized (a, b) <> Sized (c, d) = Sized (a <> c, b + d)
+  {-# INLINE (<>) #-}
+
+instance MONOID_HEAD => Monoid (Sized a) where
+  mempty  = Sized (mempty, 0)
+  mappend = (<>)
+  {-# INLINE mappend #-}
+
+class MONOID_HEAD => Buildable a where
+  type Output a :: *
+
+  str :: String -> a
+
+  sText :: S.Text -> a
+  sText = str . S.unpack
+  lText :: L.Text -> a
+  lText = str . L.unpack
+
+  singleton :: Char -> a
+  digit :: Int -> a
+  digit = singleton . intToDigit
+  {-# INLINE digit #-}
+
+  cons :: Char -> a -> a
+  cons c s = singleton c <> s
+  {-# INLINE cons #-}
+
+  repeatN :: Int -> Char -> a
+  repeatN n = str . replicate n
+
+  size :: a -> Int
+
+  finalize :: a -> Output a
+
+instance Buildable SizedStr where
+  type Output SizedStr = String
+  str a = Sized (D.fromList a, length a)
+  singleton c = Sized (D.singleton c, 1)
+  finalize = D.toList . fst . unSized
+  cons c (Sized (r, m)) = Sized (D.cons c r, m + 1)
+  repeatN n c = Sized (D.replicate n c, n)
+  size = snd . unSized
+
+instance Buildable SizedBuilder where
+  type Output SizedBuilder = Text
+  str a = Sized (fromString a, length a)
+  sText a = Sized (T.fromText a, S.length a)
+  lText a = Sized (T.fromLazyText a, fromIntegral (L.length a))
+  singleton c = Sized (T.singleton c, 1)
+  digit c = Sized (T.hexadecimal c, 1)
+  finalize = T.toLazyText . fst . unSized
+  size     = snd . unSized
diff --git a/src/Language/Haskell/Printf.hs b/src/Language/Haskell/Printf.hs
--- a/src/Language/Haskell/Printf.hs
+++ b/src/Language/Haskell/Printf.hs
@@ -3,19 +3,33 @@
 {-# 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
-    , p
-    , hp
-    ) where
+  ( 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, stdout)
+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'
@@ -29,6 +43,8 @@
 -- @
 -- %c     :: 'Char'
 -- %s     :: 'String'
+-- %q     :: 'Data.Text.Lazy.Text' -- lazy text
+-- %Q     :: 'Data.Text.Text' -- strict text
 --
 -- -- datatypes with Show instances
 -- %?     :: 'Show' a => a
@@ -49,31 +65,26 @@
 --
 -- %p     :: 'Foreign.Ptr.Ptr' a
 -- @
---
--- N.B.: For consistency with other @printf@ implementations, arguments formatted as
--- unsigned integer types will \"underflow\" if negative.
 s :: QuasiQuoter
-s =
-    quoter
-        { quoteExp =
-              \s' -> do
-                  (lhss, rhs) <- toSplices s'
-                  return $ LamE lhss rhs
-        }
+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
-        { quoteExp =
-              \s' -> do
-                  (lhss, rhs) <- toSplices s'
-                  lamE (map pure lhss) [|liftIO (hPutStr stdout $(pure rhs))|]
-        }
+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.
 --
@@ -81,20 +92,15 @@
 -- [hp|Hello, %s! (%d people greeted)|] :: 'MonadIO' m => 'System.IO.Handle' -> ... -> m ()
 -- @
 hp :: QuasiQuoter
-hp =
-    quoter
-        { quoteExp =
-              \s' -> do
-                  (lhss, rhs) <- toSplices s'
-                  h <- newName "h"
-                  lamE (varP h : map pure lhss) [|liftIO (hPutStr $(varE h) $(pure rhs))|]
-        }
+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 :: QuasiQuoter
-quoter =
-    QuasiQuoter
-        { quoteExp = undefined
-        , 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"
-        }
+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"
+  }
diff --git a/src/Language/Haskell/Printf/Geometry.hs b/src/Language/Haskell/Printf/Geometry.hs
--- a/src/Language/Haskell/Printf/Geometry.hs
+++ b/src/Language/Haskell/Printf/Geometry.hs
@@ -3,59 +3,57 @@
 
 module Language.Haskell.Printf.Geometry where
 
-import Control.Applicative ((<$>))
-import Control.Monad
-import Data.Maybe
-import Data.Monoid (mempty)
-import Data.Semigroup ((<>))
-import Language.Haskell.PrintfArg
-import Parser.Types (Adjustment(..))
-import StrUtils
+import           Control.Applicative            ( (<$>) )
+import           Control.Monad
+import           Data.Maybe
+import           Data.Monoid                    ( mempty )
+import           Data.Semigroup                 ( (<>) )
+import           Language.Haskell.PrintfArg
+import           Parser.Types                   ( Adjustment(..) )
 
-data Value = Value
-    { valArg :: PrintfArg String
-    , valPrefix :: Maybe String
-    , valSign :: Maybe String
+import qualified Buildable                     as B
+import           StrUtils
+
+data Value buf = Value
+    { valArg :: PrintfArg buf
+    , valPrefix :: Maybe buf
+    , valSign :: Maybe buf
     } deriving (Show)
 
-sign' :: (Num n, Ord n) => PrintfArg n -> Maybe String
-sign' pf
-    | value pf < 0 = Just "-"
-    | spaced pf = Just " "
-    | signed pf = Just "+"
-    | otherwise = Nothing
+sign' :: (Num n, Ord n, B.Buildable buf) => PrintfArg n -> Maybe buf
+sign' pf | value pf < 0 = Just (B.singleton '-')
+         | spaced pf    = Just (B.singleton ' ')
+         | signed pf    = Just (B.singleton '+')
+         | otherwise    = Nothing
 
-padDecimal :: (Eq v, Num v) => PrintfArg v -> String -> String
-padDecimal spec
-    | prec spec == Just 0 && value spec == 0 = const ""
-    | otherwise = maybe id (`justifyRight` '0') (prec spec)
+padDecimal :: (B.Buildable buf, Eq v, Num v) => PrintfArg v -> buf -> buf
+padDecimal spec | prec spec == Just 0 && value spec == 0 = const mempty
+                | otherwise = maybe id (`justifyRight` '0') (prec spec)
 
-prefix :: (Num n, Eq n) => String -> PrintfArg n -> Maybe String
+prefix :: (Num n, Eq n, B.Buildable buf) => buf -> PrintfArg n -> Maybe buf
 prefix s pf = guard (prefixed pf && value pf /= 0) >> Just s
 
-fromPrintfArg ::
-       (n -> String)
-    -> (PrintfArg n -> Maybe String)
-    -> (PrintfArg n -> Maybe String)
-    -> PrintfArg n
-    -> Value
+fromPrintfArg
+  :: B.Buildable buf
+  => (n -> buf)
+  -> (PrintfArg n -> Maybe buf)
+  -> (PrintfArg n -> Maybe buf)
+  -> PrintfArg n
+  -> Value buf
 fromPrintfArg f b c a = Value (f <$> a) (b a) (c a)
 
-formatOne :: Value -> String
+formatOne :: B.Buildable buf => Value buf -> buf
 formatOne Value {..}
-    | Nothing <- width valArg = prefix' <> text
-    | Just w <- width valArg =
-        case adjustment valArg of
-            Just ZeroPadded
-                | isn'tDecimal || isNothing (prec valArg) ->
-                    prefix' <> justifyRight (w - length prefix') ('0') text
-            Just LeftJustified -> justifyLeft w ' ' (prefix' <> text)
-            _ -> justify' w (prefix' <> text)
-    | otherwise = error "unreachable"
-  where
-    isn'tDecimal = fieldSpec valArg `notElem` ("diouxX" :: String)
-    justify' n
-        | n < 0 = justifyLeft (abs n) ' '
-        | otherwise = justifyRight n ' '
-    prefix' = fromMaybe mempty valSign <> fromMaybe mempty valPrefix
-    text = value valArg
+  | Nothing <- width valArg = prefix' <> text
+  | Just w <- width valArg = case adjustment valArg of
+    Just ZeroPadded | isn'tDecimal || isNothing (prec valArg) ->
+      prefix' <> justifyRight (w - B.size prefix') '0' text
+    Just LeftJustified -> justifyLeft w ' ' (prefix' <> text)
+    _                  -> justify' w (prefix' <> text)
+  | otherwise = error "unreachable"
+ where
+  isn'tDecimal = fieldSpec valArg `notElem` ("diouxX" :: String)
+  justify' n | n < 0     = justifyLeft (abs n) ' '
+             | otherwise = justifyRight n ' '
+  prefix' = fromMaybe mempty valSign <> fromMaybe mempty valPrefix
+  text    = value valArg
diff --git a/src/Language/Haskell/Printf/Lib.hs b/src/Language/Haskell/Printf/Lib.hs
--- a/src/Language/Haskell/Printf/Lib.hs
+++ b/src/Language/Haskell/Printf/Lib.hs
@@ -1,21 +1,40 @@
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE DeriveGeneric #-}
 
 module Language.Haskell.Printf.Lib
-    ( toSplices
-    ) where
+  ( toSplices
+  , OutputType (..)
+  )
+where
 
-import Control.Applicative ((<$>), pure)
-import Data.Maybe
-import Data.Semigroup ((<>))
-import Data.String (fromString)
-import Language.Haskell.Printf.Geometry (formatOne)
-import qualified Language.Haskell.Printf.Printers as Printers
-import Language.Haskell.PrintfArg
-import Language.Haskell.TH
-import Language.Haskell.TH.Syntax
-import Parser (parseStr)
-import Parser.Types hiding (lengthSpec, width)
+import           Control.Applicative            ( (<$>)
+                                                , pure
+                                                )
+import           Data.Maybe
+import           Data.Semigroup                 ( (<>) )
+import           Data.String                    ( fromString )
+import           Language.Haskell.Printf.Geometry
+                                                ( formatOne )
+import qualified Language.Haskell.Printf.Printers
+                                               as Printers
+import           Language.Haskell.PrintfArg
+import           Language.Haskell.TH
+import           Language.Haskell.TH.Syntax
+import           GHC.Generics                   ( Generic )
 
+import           Parser                         ( parseStr )
+import           Parser.Types            hiding ( lengthSpec
+                                                , width
+                                                )
+import           Buildable                      ( finalize
+                                                , SizedStr
+                                                , SizedBuilder
+                                                )
+
+data OutputType = OutputString | OutputText
+  deriving (Show, Eq, Ord, Generic, Enum, Bounded)
+
 -- | Takes a format string as input and produces a tuple @(args, outputExpr)@.
 --
 -- This function processes character escapes as they would appear in Haskell source code.
@@ -24,62 +43,67 @@
 --
 -- Use if you wish to leverage @th-printf@ in conjunction with, for example, an existing
 -- logging library.
-toSplices :: String -> Q ([Pat], Exp)
-toSplices s' =
-    case parseStr s' of
-        Left x -> fail $ show x
-        Right (y, warns) -> do
-            mapM_ (qReport False) (concat warns)
-            (lhss, rhss) <- unzip <$> mapM extractExpr y
-            rhss' <- foldr1 (\x y' -> infixApp x [|(<>)|] y') rhss
-            return (map VarP $ concat lhss, rhss')
+toSplices :: String -> OutputType -> Q ([Pat], Exp)
+toSplices s' ot = case parseStr s' of
+  Left  x          -> fail $ show x
+  Right (y, warns) -> do
+    mapM_ (qReport False) (concat warns)
+    (lhss, rhss) <- unzip <$> mapM extractExpr y
+    rhss'        <- appE
+      [|finalize|]
+      (sigE (foldr1 (\x y' -> infixApp x [|(<>)|] y') rhss) otype)
+    return (map VarP $ concat lhss, rhss')
+  where
+    otype = case ot of
+      OutputString -> [t|SizedStr|]
+      OutputText -> [t|SizedBuilder|]
 
 extractExpr :: Atom -> Q ([Name], ExpQ)
 extractExpr (Str s') = return ([], [|fromString $(stringE s')|])
 extractExpr (Arg (FormatArg flags' width' precision' spec' lengthSpec')) = do
-    (warg, wexp) <- extractArgs width'
-    (parg, pexp) <- extractArgs precision'
-    varg <- newName "arg"
-    return
-        ( catMaybes [warg, parg, Just varg]
-        , appE
-              [|formatOne|]
-              (appE
-                   formatter
-                   [|PrintfArg
-                         { flagSet = $(lift flags')
-                         , width = $(wexp)
-                         , prec = $(pexp)
-                         , value = $(varE varg)
-                         , lengthSpec = $(lift lengthSpec')
-                         , fieldSpec = $(lift spec')
-                         }|]))
-  where
-    extractArgs n =
-        case n of
-            Just Need -> do
-                a <- newName "arg"
-                pure (Just a, [|Just (fromInteger (fromIntegral $(varE a)))|])
-            Just (Given n') -> pure (Nothing, [|Just $(litE $ integerL n')|])
-            Nothing -> pure (Nothing, [|Nothing|])
-    formatter =
-        case spec' of
-            's' -> [|Printers.printfString|]
-            '?' -> [|Printers.printfShow|]
-            'd' -> [|Printers.printfDecimal|]
-            'i' -> [|Printers.printfDecimal|]
-            'p' -> [|Printers.printfPtr|]
-            'c' -> [|Printers.printfChar|]
-            'u' -> [|Printers.printfUnsigned|]
-            'x' -> [|Printers.printfHex False|]
-            'X' -> [|Printers.printfHex True|]
-            'o' -> [|Printers.printfOctal|]
-            'f' -> [|Printers.printfFloating False|]
-            'F' -> [|Printers.printfFloating True|]
-            'e' -> [|Printers.printfScientific False|]
-            'E' -> [|Printers.printfScientific True|]
-            'g' -> [|Printers.printfGeneric False|]
-            'G' -> [|Printers.printfGeneric True|]
-            'a' -> [|Printers.printfFloatHex False|]
-            'A' -> [|Printers.printfFloatHex True|]
-            _ -> undefined
+  (warg, wexp) <- extractArgs width'
+  (parg, pexp) <- extractArgs precision'
+  varg         <- newName "arg"
+  return
+    ( catMaybes [warg, parg, Just varg]
+    , appE
+      [|formatOne|]
+      (appE
+        formatter
+        [|PrintfArg { flagSet = $(lift flags')
+                    , width = $(wexp)
+                    , prec = $(pexp)
+                    , value = $(varE varg)
+                    , lengthSpec = $(lift lengthSpec')
+                    , fieldSpec = $(lift spec') }|]
+      )
+    )
+ where
+  extractArgs n = case n of
+    Just Need -> do
+      a <- newName "arg"
+      pure (Just a, [|Just (fromInteger (fromIntegral $(varE a)))|])
+    Just (Given n') -> pure (Nothing, [|Just $(litE $ integerL n')|])
+    Nothing         -> pure (Nothing, [|Nothing|])
+  formatter = case spec' of
+    's' -> [|Printers.printfString|]
+    'q' -> [|Printers.printfLazyText|]
+    'Q' -> [|Printers.printfStrictText|]
+    '?' -> [|Printers.printfShow|]
+    'd' -> [|Printers.printfDecimal|]
+    'i' -> [|Printers.printfDecimal|]
+    'p' -> [|Printers.printfPtr|]
+    'c' -> [|Printers.printfChar|]
+    'u' -> [|Printers.printfUnsigned|]
+    'x' -> [|Printers.printfHex False|]
+    'X' -> [|Printers.printfHex True|]
+    'o' -> [|Printers.printfOctal|]
+    'f' -> [|Printers.printfFloating False|]
+    'F' -> [|Printers.printfFloating True|]
+    'e' -> [|Printers.printfScientific False|]
+    'E' -> [|Printers.printfScientific True|]
+    'g' -> [|Printers.printfGeneric False|]
+    'G' -> [|Printers.printfGeneric True|]
+    'a' -> [|Printers.printfFloatHex False|]
+    'A' -> [|Printers.printfFloatHex True|]
+    _   -> undefined
diff --git a/src/Language/Haskell/Printf/Printers.hs b/src/Language/Haskell/Printf/Printers.hs
--- a/src/Language/Haskell/Printf/Printers.hs
+++ b/src/Language/Haskell/Printf/Printers.hs
@@ -1,149 +1,171 @@
 {-# OPTIONS_GHC -fno-warn-missing-signatures #-}
-{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
 {-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE ExplicitForAll #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 module Language.Haskell.Printf.Printers where
 
-import Control.Applicative ((<$>), pure)
-import Data.Char
-import Data.List
-import Data.String (fromString)
-import Foreign.Ptr
-import GHC.Float (FFFormat(..))
-import Language.Haskell.Printf.Geometry
-import Language.Haskell.PrintfArg
-import NumUtils
-import qualified Parser.Types as P
+import           Control.Applicative            ( (<$>) )
+import           Data.Char
+import           Data.String                    ( fromString )
+import           Data.Maybe                     ( fromMaybe )
+import           Foreign.Ptr
+import           GHC.Float                      ( FFFormat(..) )
+import           Language.Haskell.Printf.Geometry
+import           Language.Haskell.PrintfArg
+import qualified Data.Text.Lazy                as L
+import qualified Data.Text                     as S
+import           Math.NumberTheory.Logarithms
 
-type Printer n = PrintfArg n -> Value
+import           NumUtils
+import qualified Parser.Types                  as P
+import qualified Buildable                     as B
 
-printfString :: Printer String
-printfString spec =
-    Value
-        { valArg =
-              case prec spec of
-                  Nothing -> spec
-                  Just c -> take c <$> spec
-        , valPrefix = Nothing
-        , valSign = Nothing
-        }
+type Printer n buf = PrintfArg n -> Value buf
 
-printfShow :: Show a => Printer a
+printfString :: B.Buildable buf => Printer String buf
+printfString spec = Value
+  { valArg    = case prec spec of
+                  Nothing -> B.str <$> spec
+                  Just c  -> B.str . take c <$> spec
+  , valPrefix = Nothing
+  , valSign   = Nothing
+  }
+
+printfStrictText :: B.Buildable buf => Printer S.Text buf
+printfStrictText spec = Value
+  { valArg    = case prec spec of
+                  Nothing -> B.sText <$> spec
+                  Just c  -> B.sText . S.take c <$> spec
+  , valPrefix = Nothing
+  , valSign   = Nothing
+  }
+
+printfLazyText :: B.Buildable buf => Printer L.Text buf
+printfLazyText spec = Value
+  { valArg    = case prec spec of
+                  Nothing -> B.lText <$> spec
+                  Just c  -> B.lText . L.take (fromIntegral c) <$> spec
+  , valPrefix = Nothing
+  , valSign   = Nothing
+  }
+
+printfShow :: (B.Buildable buf, Show a) => Printer a buf
 printfShow spec = printfString (fromString . show <$> spec)
 
-printfChar :: Printer Char
-printfChar spec = Value {valArg = pure <$> spec, valPrefix = Nothing, valSign = Nothing}
+printfChar :: B.Buildable buf => Printer Char buf
+printfChar spec = Value { valArg    = B.singleton <$> spec
+                        , valPrefix = Nothing
+                        , valSign   = Nothing
+                        }
 
-printfPtr :: Printer (Ptr a)
-printfPtr spec =
-    Value
-        { valArg =
-              PrintfArg
-                  { width = width spec
-                  , prec = Nothing
-                  , flagSet = P.emptyFlagSet {P.prefixed = True}
+{-# ANN printfPtr ("HLint: ignore Use showHex" :: String) #-}
+printfPtr :: B.Buildable 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 (toInt $ value spec)
+                  , fieldSpec  = 'p'
+                  , value = showIntAtBase 16 intToDigit (ptrToWordPtr $ value spec)
                   }
-        , valPrefix = Just "0x"
-        , valSign = Nothing
-        }
-  where
-    toInt x = x `minusPtr` nullPtr
+  , valPrefix = Just (B.str "0x")
+  , valSign   = Nothing
+  }
 
-printfDecimal spec =
-    Value
-        { valArg = padDecimal spec . showIntAtBase 10 intToDigit . abs <$> spec
-        , valPrefix = Nothing
-        , valSign = sign' spec
-        }
+printfDecimal spec = Value
+  { valArg    = padDecimal spec . showIntAtBase 10 intToDigit . abs <$> spec
+  , valPrefix = Nothing
+  , valSign   = sign' spec
+  }
 
-fmtUnsigned ::
-       forall a. (Bounded a, Integral a)
-    => (Integer -> String)
-    -> (PrintfArg a -> Maybe String)
-    -> Printer a
-fmtUnsigned shower p spec =
-    Value
-        { valArg = padDecimal spec . shower . clamp <$> spec
-        , valPrefix = p spec
-        , valSign = Nothing
-        }
-  where
-    lb = minBound :: a
-    clamp :: a -> Integer
-    clamp x
-        | x < 0 = toInteger x + (-2 * toInteger lb)
-        | otherwise = toInteger x
+fmtUnsigned
+  :: (Bounded a, Integral a, B.Buildable 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 b =
-    fmtUnsigned
-        showHex
-        (prefix
-             (if b
-                  then "0X"
-                  else "0x"))
-  where
-    showHex =
-        showIntAtBase
-            16
-            ((if b
-                  then toUpper
-                  else id) .
-             intToDigit)
+printfHex b = fmtUnsigned showHex (prefix (if b then "0X" else "0x"))
+  where showHex = showIntAtBase 16 ((if b then toUpper else id) . intToDigit)
 
 printfUnsigned = fmtUnsigned (showIntAtBase 10 intToDigit) (const Nothing)
 
-printfOctal spec
-    | "0" `isPrefixOf` value valArg = v
-    | otherwise = v {valPrefix = prefix "0" spec}
-  where
-    v@Value {..} = fmtUnsigned (showIntAtBase 8 intToDigit) (const Nothing) spec
+-- 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 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 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
+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 upperFlag spec =
-    Value {valArg = showSci . abs <$> spec, valPrefix = Nothing, valSign = sign' spec}
-  where
-    showSci =
-        formatRealFloatAlt
-            FFExponent
-            (fromIntegral <$> prec spec)
-            (prefixed spec)
-            upperFlag
+printfScientific upperFlag spec = Value { valArg    = showSci . abs <$> spec
+                                        , valPrefix = Nothing
+                                        , valSign   = sign' spec
+                                        }
+ where
+  showSci = formatRealFloatAlt FFExponent
+                               (fromIntegral <$> prec spec)
+                               (prefixed spec)
+                               upperFlag
 
-printfGeneric upperFlag spec =
-    Value {valArg = showSci . abs <$> spec, valPrefix = Nothing, valSign = sign' spec}
-  where
-    showSci =
-        formatRealFloatAlt
-            FFGeneric
-            (fromIntegral <$> prec spec)
-            (prefixed spec)
-            upperFlag
+printfGeneric upperFlag spec = Value { valArg    = showSci . abs <$> spec
+                                     , valPrefix = Nothing
+                                     , valSign   = sign' spec
+                                     }
+ where
+  showSci = formatRealFloatAlt FFGeneric
+                               (fromIntegral <$> prec spec)
+                               (prefixed spec)
+                               upperFlag
 
-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
+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
diff --git a/src/Language/Haskell/PrintfArg.hs b/src/Language/Haskell/PrintfArg.hs
--- a/src/Language/Haskell/PrintfArg.hs
+++ b/src/Language/Haskell/PrintfArg.hs
@@ -2,7 +2,7 @@
 
 module Language.Haskell.PrintfArg where
 
-import qualified Parser.Types as P
+import qualified Parser.Types                  as P
 
 data PrintfArg v = PrintfArg
     { flagSet :: P.FlagSet
diff --git a/src/NumUtils.hs b/src/NumUtils.hs
--- a/src/NumUtils.hs
+++ b/src/NumUtils.hs
@@ -1,153 +1,134 @@
 {-# OPTIONS_GHC -fno-warn-dodgy-imports #-}
 {-# LANGUAGE NoMonomorphismRestriction #-}
-{-# LANGUAGE OverloadedStrings #-}
 
 module NumUtils where
 
-import Control.Applicative (pure)
-import Data.Bits
-import Data.Char
-import Data.Foldable
-import Data.Ord
-import Data.Semigroup ((<>))
-import Data.String (fromString)
-import Data.Tuple
-import GHC.Base hiding ((<>), foldr)
-import GHC.Float (FFFormat(..), roundTo)
-import Numeric (floatToDigits)
-import Prelude hiding (exp, foldr)
-import StrUtils
+import           Data.Bits
+import           Data.Char
+import           Data.Foldable
+import           Data.Ord
+import           Data.Semigroup                 ( (<>) )
+import           Data.Tuple
+import           GHC.Float                      ( FFFormat(..)
+                                                , roundTo
+                                                )
+import           Numeric                        ( floatToDigits )
+import           Prelude                 hiding ( exp
+                                                , foldr
+                                                , (<>)
+                                                )
 
-showIntAtBase :: (Show a, Integral a) => a -> (Int -> Char) -> a -> String
-showIntAtBase base toChr n0
-    | base <= 1 = error "unsupported base"
-    | n0 < 0 = error $ "negative number " ++ show n0
-    | otherwise = showIt (quotRem n0 base) ""
-  where
-    showIt (n, d) r =
-        case n of
-            0 -> r'
-            _ -> showIt (quotRem n base) r'
-      where
-        c = id (toChr (fromIntegral d))
-        r' = (:) c r
+import qualified Buildable                     as B
+import           StrUtils
 
-formatRealFloatAlt :: RealFloat a => FFFormat -> Maybe Int -> Bool -> Bool -> a -> String
+showIntAtBase
+  :: (B.Buildable buf, Show a, Integral a) => a -> (Int -> Char) -> a -> buf
+showIntAtBase base toChr n0 | base <= 1 = error "unsupported base"
+                            | n0 < 0    = error $ "negative number " ++ show n0
+                            | otherwise = showIt (quotRem n0 base) mempty
+ where
+  showIt (n, d) r = case n of
+    0 -> r'
+    _ -> showIt (quotRem n base) r'
+    where r' = B.cons (toChr (fromIntegral d)) r
+
+formatRealFloatAlt
+  :: (B.Buildable buf, RealFloat a)
+  => FFFormat
+  -> Maybe Int
+  -> Bool
+  -> Bool
+  -> a
+  -> buf
 formatRealFloatAlt fmt decs forceDot upper x
-    | isNaN x = "NaN"
-    | isInfinite x =
-        if x < 0
-            then "-Infinity"
-            else "Infinity"
-    | x < 0 || isNegativeZero x = (:) (id '-') (doFmt fmt (floatToDigits 10 (-x)) False)
-    | otherwise = doFmt fmt (floatToDigits 10 x) False
-  where
-    eChar
-        | upper = id 'E'
-        | otherwise = id 'e'
-    doFmt FFFixed (digs, exp) fullRounding
-        | exp < 0 = doFmt FFFixed (replicate (negate exp) 0 ++ digs, 0) fullRounding
-        | null part =
-            fromDigits False whole <>
-            (if forceDot
-                 then "."
-                 else "")
-        | null whole = "0." <> fromDigits False part
-        | otherwise = fromDigits False whole <> "." <> fromDigits False part
-      where
-        (whole, part) =
-            uncurry (flip splitAt) (toRoundedDigits decs (digs, exp) fullRounding)
-    doFmt FFExponent ([0], _) _
-        | forceDot = "0.e+00"
-        | otherwise = "0e+00"
-    doFmt FFExponent (digs, exp) fullRounding = shownDigs <> (:) eChar shownExponent
-      where
-        shownDigs =
-            case digs' of
-                [] -> undefined
-                [x'] ->
-                    pure (id (intToDigit x')) <>
-                    (if forceDot
-                         then "."
-                         else "")
-                (x':xs) -> (:) (id (intToDigit x')) ((:) (id '.') (fromDigits False xs))
-        digs' =
-            case decs of
-                Just n ->
-                    case roundTo
-                             10
-                             (if fullRounding
-                                  then min (length digs) n
-                                  else n + 1)
-                             digs of
-                        (1, xs) -> 1 : xs
-                        (_, ys) -> ys
-                Nothing -> digs
-        exp' = exp - 1
-        shownExponent =
-            (:)
-                (id $
-                 if exp' < 0
-                     then '-'
-                     else '+') $
-            justifyRight 2 (id '0') $ showIntAtBase 10 intToDigit $ abs exp'
-    doFmt FFGeneric d _ =
-        minimumBy (comparing length) [doFmt FFFixed d True, doFmt FFExponent d True]
+  | isNaN x = B.str "NaN"
+  | isInfinite x = B.str $ if x < 0 then "-Infinity" else "Infinity"
+  | x < 0 || isNegativeZero x = B.cons
+    '-'
+    (doFmt fmt (floatToDigits 10 (-x)) False)
+  | otherwise = doFmt fmt (floatToDigits 10 x) False
+ where
+  eChar | upper     = 'E'
+        | otherwise = 'e'
+  doFmt FFFixed (digs, exp) fullRounding
+    | exp < 0
+    = doFmt FFFixed (replicate (negate exp) 0 ++ digs, 0) fullRounding
+    | null part
+    = fromDigits False whole <> (if forceDot then B.singleton '.' else mempty)
+    | null whole
+    = B.str "0." <> fromDigits False part
+    | otherwise
+    = fromDigits False whole <> B.singleton '.' <> fromDigits False part
+   where
+    (whole, part) =
+      uncurry (flip splitAt) (toRoundedDigits decs (digs, exp) fullRounding)
+  doFmt FFExponent ([0], _) _ | forceDot  = B.str "0.e+00"
+                              | otherwise = B.str "0e+00"
+  doFmt FFExponent (digs, exp) fullRounding =
+    shownDigs <> B.cons eChar shownExponent
+   where
+    shownDigs = case digs' of
+      [] -> undefined
+      [x'] ->
+        B.cons (intToDigit x') (if forceDot then B.singleton '.' else mempty)
+      (x' : xs) -> B.cons (intToDigit x') (B.cons '.' (fromDigits False xs))
+    digs' = case decs of
+      Just n ->
+        case
+            roundTo 10
+                    (if fullRounding then min (length digs) n else n + 1)
+                    digs
+          of
+            (1, xs) -> 1 : xs
+            (_, ys) -> ys
+      Nothing -> digs
+    exp' = exp - 1
+    shownExponent =
+      B.cons (if exp' < 0 then '-' else '+')
+        $ justifyRight 2 '0'
+        $ showIntAtBase 10 intToDigit
+        $ abs exp'
+  doFmt FFGeneric d _ =
+    minimumBy (comparing B.size) [doFmt FFFixed d True, doFmt FFExponent d True]
 
 toRoundedDigits :: Maybe Int -> ([Int], Int) -> Bool -> ([Int], Int)
-toRoundedDigits Nothing (digs, exp) _ = (digs, exp)
+toRoundedDigits Nothing     (digs, exp) _            = (digs, exp)
 toRoundedDigits (Just prec) (digs, exp) fullRounding = (digs', exp + overflow)
-  where
-    (overflow, digs') =
-        roundTo
-            10
-            (if fullRounding && prec > exp
-                 then min (length digs) prec
-                 else prec + exp)
-            digs
+ where
+  (overflow, digs') = roundTo
+    10
+    (if fullRounding && prec > exp then min (length digs) prec else prec + exp)
+    digs
 
-fromDigits :: Bool -> [Int] -> String
+fromDigits :: B.Buildable buf => Bool -> [Int] -> buf
 fromDigits upper =
-    foldr
-        ((:) .
-         id .
-         (if upper
-              then toUpper
-              else id) .
-         intToDigit)
-        []
+  foldr (B.cons . (if upper then toUpper else id) . intToDigit) mempty
 
-formatHexFloat :: RealFloat a => Maybe Int -> Bool -> Bool -> a -> String
+formatHexFloat
+  :: (B.Buildable buf, RealFloat a) => Maybe Int -> Bool -> Bool -> a -> buf
 formatHexFloat decs alt upper x = doFmt (floatToDigits 2 x)
-  where
-    pChar
-        | upper = id 'P'
-        | otherwise = id 'p'
-    doFmt ([], _) = undefined
-    doFmt ([0], 0) = "0" <> pure pChar <> "+0"
-    doFmt (_:bits, exp) =
-        fromString (show (1 + overflow)) <>
-        (if not (null hexDigits) || alt
-             then "."
-             else "") <>
-        fromDigits upper hexDigits <>
-        pure pChar <>
-        (if exp > 0
-             then "+"
-             else "") <>
-        fromString (show (exp - 1))
-      where
-        hexDigits' = go bits
-        (overflow, hexDigits) =
-            case decs of
-                Just n ->
-                    case roundTo 16 n hexDigits' of
-                        (1, _:digs) -> (1, digs)
-                        x' -> x'
-                Nothing -> (0, hexDigits')
-        go (a:b:c:d:xs) =
-            ((a `shiftL` 3) .|. (b `shiftL` 2) .|. (c `shiftL` 1) .|. d) : go xs
-        go [a, b, c] = go [a, b, c, 0]
-        go [a, b] = go [a, b, 0, 0]
-        go [a] = go [a, 0, 0, 0]
-        go [] = []
+ where
+  pChar | upper     = 'P'
+        | otherwise = 'p'
+  doFmt ([] , _) = undefined
+  doFmt ([0], 0) = B.cons '0' (B.cons pChar (B.str "+0"))
+  doFmt (_ : bits, exp) =
+    B.str "1"
+      <> (if not (null hexDigits) || alt then B.singleton '.' else mempty)
+      <> fromDigits upper hexDigits
+      <> B.singleton pChar
+      <> (if exp > 0 then B.singleton '+' else mempty)
+      <> B.str (show (exp - 1 + overflow))
+   where
+    hexDigits'            = go bits
+    (overflow, hexDigits) = case decs of
+      Just n -> case roundTo 16 n hexDigits' of
+        (1, _ : digs) -> (1, digs)
+        x'            -> x'
+      Nothing -> (0, hexDigits')
+    go (a : b : c : d : xs) =
+      ((a `shiftL` 3) .|. (b `shiftL` 2) .|. (c `shiftL` 1) .|. d) : go xs
+    go [a, b, c] = go [a, b, c, 0]
+    go [a, b]    = go [a, b, 0, 0]
+    go [a]       = go [a, 0, 0, 0]
+    go []        = []
diff --git a/src/StrUtils.hs b/src/StrUtils.hs
--- a/src/StrUtils.hs
+++ b/src/StrUtils.hs
@@ -1,15 +1,14 @@
 module StrUtils where
 
-justifyLeft :: Int -> Char -> String -> String
-justifyLeft n c s
-    | diff <= 0 = s
-    | otherwise = s ++ replicate diff c
-  where
-    diff = n - length s
+import           Data.Semigroup                 ( (<>) )
+import           Buildable
 
-justifyRight :: Int -> Char -> String -> String
-justifyRight n c s
-    | diff <= 0 = s
-    | otherwise = replicate diff c ++ s
-  where
-    diff = n - length s
+justifyLeft :: Buildable a => Int -> Char -> a -> a
+justifyLeft n c s | diff <= 0 = s
+                  | otherwise = s <> repeatN diff c
+  where diff = n - size s
+
+justifyRight :: Buildable a => Int -> Char -> a -> a
+justifyRight n c s | diff <= 0 = s
+                   | otherwise = repeatN diff c <> s
+  where diff = n - size s
diff --git a/tests/GeneratedSpec.hs b/tests/GeneratedSpec.hs
deleted file mode 100644
--- a/tests/GeneratedSpec.hs
+++ /dev/null
@@ -1,452 +0,0 @@
--- This file contains autogenerated erroneous printf strings, which th-printf
--- will warn about, but they shouldn't break a -Werror build
-{-# OPTIONS_GHC -Wwarn #-}
-{-# LANGUAGE QuasiQuotes #-}
-
-module GeneratedSpec where
-
-import Data.Int
-import Data.Word
-import Language.Haskell.Printf
-import Test.HUnit
-import Test.Hspec
-
--- XXX This code generated automatically by gen-testcases.hs
--- from ../../printf-tests.txt . You probably do not want to
--- manually edit this file.
-spec = do
-    it "test-case #1" $ [s|%.*f|] (2 :: Int32) (0.33333333 :: Double) @?= "0.33"
-    it "test-case #2" $ [s|%.3s|] "foobar" @?= "foo"
-    it "test-case #3" $ [s|%10.5d|] (4 :: Int32) @?= "     00004"
-    it "test-case #4" $ [s|% d|] (42 :: Int32) @?= " 42"
-    it "test-case #5" $ [s|% d|] (-42 :: Int32) @?= "-42"
-    it "test-case #6" $ [s|% 5d|] (42 :: Int32) @?= "   42"
-    it "test-case #7" $ [s|% 5d|] (-42 :: Int32) @?= "  -42"
-    it "test-case #8" $ [s|% 15d|] (42 :: Int32) @?= "             42"
-    it "test-case #9" $ [s|% 15d|] (-42 :: Int32) @?= "            -42"
-    it "test-case #10" $ [s|%+d|] (42 :: Int32) @?= "+42"
-    it "test-case #11" $ [s|%+d|] (-42 :: Int32) @?= "-42"
-    it "test-case #12" $ [s|%+5d|] (42 :: Int32) @?= "  +42"
-    it "test-case #13" $ [s|%+5d|] (-42 :: Int32) @?= "  -42"
-    it "test-case #14" $ [s|%+15d|] (42 :: Int32) @?= "            +42"
-    it "test-case #15" $ [s|%+15d|] (-42 :: Int32) @?= "            -42"
-    it "test-case #16" $ [s|%0d|] (42 :: Int32) @?= "42"
-    it "test-case #17" $ [s|%0d|] (-42 :: Int32) @?= "-42"
-    it "test-case #18" $ [s|%05d|] (42 :: Int32) @?= "00042"
-    it "test-case #19" $ [s|%05d|] (-42 :: Int32) @?= "-0042"
-    it "test-case #20" $ [s|%015d|] (42 :: Int32) @?= "000000000000042"
-    it "test-case #21" $ [s|%015d|] (-42 :: Int32) @?= "-00000000000042"
-    it "test-case #22" $ [s|%-d|] (42 :: Int32) @?= "42"
-    it "test-case #23" $ [s|%-d|] (-42 :: Int32) @?= "-42"
-    it "test-case #24" $ [s|%-5d|] (42 :: Int32) @?= "42   "
-    it "test-case #25" $ [s|%-5d|] (-42 :: Int32) @?= "-42  "
-    it "test-case #26" $ [s|%-15d|] (42 :: Int32) @?= "42             "
-    it "test-case #27" $ [s|%-15d|] (-42 :: Int32) @?= "-42            "
-    it "test-case #28" $ [s|%-0d|] (42 :: Int32) @?= "42"
-    it "test-case #29" $ [s|%-0d|] (-42 :: Int32) @?= "-42"
-    it "test-case #30" $ [s|%-05d|] (42 :: Int32) @?= "42   "
-    it "test-case #31" $ [s|%-05d|] (-42 :: Int32) @?= "-42  "
-    it "test-case #32" $ [s|%-015d|] (42 :: Int32) @?= "42             "
-    it "test-case #33" $ [s|%-015d|] (-42 :: Int32) @?= "-42            "
-    it "test-case #34" $ [s|%0-d|] (42 :: Int32) @?= "42"
-    it "test-case #35" $ [s|%0-d|] (-42 :: Int32) @?= "-42"
-    it "test-case #36" $ [s|%0-5d|] (42 :: Int32) @?= "42   "
-    it "test-case #37" $ [s|%0-5d|] (-42 :: Int32) @?= "-42  "
-    it "test-case #38" $ [s|%0-15d|] (42 :: Int32) @?= "42             "
-    it "test-case #39" $ [s|%0-15d|] (-42 :: Int32) @?= "-42            "
-    it "test-case #43" $ [s|%.2f|] (42.8952 :: Double) @?= "42.90"
-    it "test-case #44" $ [s|%.2F|] (42.8952 :: Double) @?= "42.90"
-    it "test-case #45" $ [s|%.10f|] (42.8952 :: Double) @?= "42.8952000000"
-    it "test-case #46" $ [s|%1.2f|] (42.8952 :: Double) @?= "42.90"
-    it "test-case #47" $ [s|%6.2f|] (42.8952 :: Double) @?= " 42.90"
-    it "test-case #49" $ [s|%+6.2f|] (42.8952 :: Double) @?= "+42.90"
-    it "test-case #50" $ [s|%5.10f|] (42.8952 :: Double) @?= "42.8952000000"
-  -- 51: anti-test
-  -- 52: anti-test
-  -- 53: excluded for Haskell
-  -- 55: excluded for Haskell
-  -- 56: excluded for Haskell
-  -- 58: excluded for Haskell
-  -- 59: excluded for Haskell
-    it "test-case #60" $ [s|%*s|] (4 :: Int32) "foo" @?= " foo"
-    it "test-case #61" $
-        [s|%*.*f|] (10 :: Int32) (2 :: Int32) (3.14159265 :: Double) @?= "      3.14"
-    it "test-case #63" $
-        [s|%-*.*f|] (10 :: Int32) (2 :: Int32) (3.14159265 :: Double) @?= "3.14      "
-  -- 64: anti-test
-  -- 65: anti-test
-    it "test-case #66" $ [s|+%s+|] "hello" @?= "+hello+"
-    it "test-case #67" $ [s|+%d+|] (10 :: Int32) @?= "+10+"
-    it "test-case #68" $ [s|%c|] 'a' @?= "a"
-  -- unsound test: it "test-case #69" $ [s|%c|] (32 :: Int32) @?= " "
-  -- unsound test: it "test-case #70" $ [s|%c|] (36 :: Int32) @?= "$"
-    it "test-case #71" $ [s|%d|] (10 :: Int32) @?= "10"
-  -- 72: anti-test
-  -- 73: anti-test
-  -- 74: excluded for Haskell
-  -- 75: excluded for Haskell
-    it "test-case #76" $
-        [s|%+#22.15e|] (7.89456123e8 :: Double) @?= "+7.894561230000000e+08"
-    it "test-case #77" $
-        [s|%-#22.15e|] (7.89456123e8 :: Double) @?= "7.894561230000000e+08 "
-    it "test-case #78" $
-        [s|%#22.15e|] (7.89456123e8 :: Double) @?= " 7.894561230000000e+08"
-    it "test-case #79" $ [s|%#1.1g|] (7.89456123e8 :: Double) @?= "8.e+08"
-    it "test-case #81" $ [s|%+8lld|] (100 :: Int64) @?= "    +100"
-    it "test-case #82" $ [s|%+.8lld|] (100 :: Int64) @?= "+00000100"
-    it "test-case #83" $ [s|%+10.8lld|] (100 :: Int64) @?= " +00000100"
-  -- 84: excluded for Haskell
-    it "test-case #85" $ [s|%-1.5lld|] (-100 :: Int64) @?= "-00100"
-    it "test-case #86" $ [s|%5lld|] (100 :: Int64) @?= "  100"
-    it "test-case #87" $ [s|%5lld|] (-100 :: Int64) @?= " -100"
-    it "test-case #88" $ [s|%-5lld|] (100 :: Int64) @?= "100  "
-    it "test-case #89" $ [s|%-5lld|] (-100 :: Int64) @?= "-100 "
-    it "test-case #90" $ [s|%-.5lld|] (100 :: Int64) @?= "00100"
-    it "test-case #91" $ [s|%-.5lld|] (-100 :: Int64) @?= "-00100"
-    it "test-case #92" $ [s|%-8.5lld|] (100 :: Int64) @?= "00100   "
-    it "test-case #93" $ [s|%-8.5lld|] (-100 :: Int64) @?= "-00100  "
-    it "test-case #94" $ [s|%05lld|] (100 :: Int64) @?= "00100"
-    it "test-case #95" $ [s|%05lld|] (-100 :: Int64) @?= "-0100"
-    it "test-case #96" $ [s|% lld|] (100 :: Int64) @?= " 100"
-    it "test-case #97" $ [s|% lld|] (-100 :: Int64) @?= "-100"
-    it "test-case #98" $ [s|% 5lld|] (100 :: Int64) @?= "  100"
-    it "test-case #99" $ [s|% 5lld|] (-100 :: Int64) @?= " -100"
-    it "test-case #100" $ [s|% .5lld|] (100 :: Int64) @?= " 00100"
-    it "test-case #101" $ [s|% .5lld|] (-100 :: Int64) @?= "-00100"
-    it "test-case #102" $ [s|% 8.5lld|] (100 :: Int64) @?= "   00100"
-    it "test-case #103" $ [s|% 8.5lld|] (-100 :: Int64) @?= "  -00100"
-    it "test-case #104" $ [s|%.0lld|] (0 :: Int64) @?= ""
-    it "test-case #105" $ [s|%#+21.18llx|] (-100 :: Int64) @?= " 0x00ffffffffffffff9c"
-    it "test-case #106" $ [s|%#.25llo|] (-100 :: Int64) @?= "0001777777777777777777634"
-    it "test-case #107" $ [s|%#+24.20llo|] (-100 :: Int64) @?= " 01777777777777777777634"
-    it "test-case #108" $ [s|%#+18.21llX|] (-100 :: Int64) @?= "0X00000FFFFFFFFFFFFFF9C"
-    it "test-case #109" $ [s|%#+20.24llo|] (-100 :: Int64) @?= "001777777777777777777634"
-    it "test-case #110" $ [s|%#+25.22llu|] (-1 :: Int64) @?= "   0018446744073709551615"
-    it "test-case #111" $ [s|%#+25.22llu|] (-1 :: Int64) @?= "   0018446744073709551615"
-    it "test-case #112" $
-        [s|%#+30.25llu|] (-1 :: Int64) @?= "     0000018446744073709551615"
-    it "test-case #113" $ [s|%+#25.22lld|] (-1 :: Int64) @?= "  -0000000000000000000001"
-    it "test-case #114" $ [s|%#-8.5llo|] (100 :: Int64) @?= "00144   "
-    it "test-case #115" $ [s|%#-+ 08.5lld|] (100 :: Int64) @?= "+00100  "
-    it "test-case #116" $ [s|%#-+ 08.5lld|] (100 :: Int64) @?= "+00100  "
-    it "test-case #117" $
-        [s|%.40lld|] (1 :: Int64) @?= "0000000000000000000000000000000000000001"
-    it "test-case #118" $
-        [s|% .40lld|] (1 :: Int64) @?= " 0000000000000000000000000000000000000001"
-    it "test-case #119" $
-        [s|% .40d|] (1 :: Int32) @?= " 0000000000000000000000000000000000000001"
-  -- 121: excluded for Haskell
-  -- 124: excluded for Haskell
-    it "test-case #125" $ [s|% d|] (1 :: Int32) @?= " 1"
-    it "test-case #126" $ [s|%+ d|] (1 :: Int32) @?= "+1"
-    it "test-case #129" $ [s|%#012x|] (1 :: Int32) @?= "0x0000000001"
-    it "test-case #130" $ [s|%#04.8x|] (1 :: Int32) @?= "0x00000001"
-    it "test-case #131" $ [s|%#-08.2x|] (1 :: Int32) @?= "0x01    "
-    it "test-case #132" $ [s|%#08o|] (1 :: Int32) @?= "00000001"
-  -- 133: excluded for Haskell
-  -- 137: excluded for Haskell
-    it "test-case #142" $ [s|%.1s|] "foo" @?= "f"
-    it "test-case #143" $ [s|%.*s|] (1 :: Int32) "foo" @?= "f"
-    it "test-case #144" $ [s|%*s|] (-5 :: Int32) "foo" @?= "foo  "
-    it "test-case #145" $ [s|hello|] @?= "hello"
-  -- 147: excluded for Haskell
-    it "test-case #148" $ [s|%3c|] 'a' @?= "  a"
-    it "test-case #149" $ [s|%3d|] (1234 :: Int32) @?= "1234"
-  -- 150: excluded for Haskell
-    it "test-case #152" $ [s|%-1d|] (2 :: Int32) @?= "2"
-    it "test-case #153" $ [s|%2.4f|] (8.6 :: Double) @?= "8.6000"
-    it "test-case #154" $ [s|%0f|] (0.6 :: Double) @?= "0.600000"
-    it "test-case #155" $ [s|%.0f|] (0.6 :: Double) @?= "1"
-    it "test-case #156" $ [s|%2.4e|] (8.6 :: Double) @?= "8.6000e+00"
-    it "test-case #157" $ [s|% 2.4e|] (8.6 :: Double) @?= " 8.6000e+00"
-    it "test-case #159" $ [s|% 2.4e|] (-8.6 :: Double) @?= "-8.6000e+00"
-    it "test-case #160" $ [s|%+2.4e|] (8.6 :: Double) @?= "+8.6000e+00"
-    it "test-case #161" $ [s|%2.4g|] (8.6 :: Double) @?= "8.6"
-    it "test-case #162" $ [s|%-i|] (-1 :: Int32) @?= "-1"
-    it "test-case #163" $ [s|%-i|] (1 :: Int32) @?= "1"
-    it "test-case #164" $ [s|%+i|] (1 :: Int32) @?= "+1"
-    it "test-case #165" $ [s|%o|] (10 :: Int32) @?= "12"
-  -- 166: excluded for Haskell
-  -- 167: excluded for Haskell
-    it "test-case #169" $ [s|%s|] "%%%%" @?= "%%%%"
-    it "test-case #170" $ [s|%u|] (-1 :: Int32) @?= "4294967295"
-  -- 171: excluded for Haskell
-  -- 172: excluded for Haskell
-  -- 173: excluded for Haskell
-  -- 174: excluded for Haskell
-  -- 176: excluded for Haskell
-    it "test-case #177" $ [s|%%0|] @?= "%0"
-  -- 178: excluded for Haskell
-  -- unsound test: it "test-case #179" $ [s|%hhx|] 'a' @?= "61"
-    it "test-case #181" $ [s|Hallo heimur|] @?= "Hallo heimur"
-    it "test-case #182" $ [s|%s|] "Hallo heimur" @?= "Hallo heimur"
-    it "test-case #183" $ [s|%d|] (1024 :: Int32) @?= "1024"
-    it "test-case #184" $ [s|%d|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #185" $ [s|%i|] (1024 :: Int32) @?= "1024"
-    it "test-case #186" $ [s|%i|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #187" $ [s|%u|] (1024 :: Int32) @?= "1024"
-    it "test-case #188" $ [s|%u|] (4294966272 :: Word32) @?= "4294966272"
-    it "test-case #189" $ [s|%o|] (511 :: Int32) @?= "777"
-    it "test-case #190" $ [s|%o|] (4294966785 :: Word32) @?= "37777777001"
-    it "test-case #191" $ [s|%x|] (305441741 :: Int32) @?= "1234abcd"
-    it "test-case #192" $ [s|%x|] (3989525555 :: Word32) @?= "edcb5433"
-    it "test-case #193" $ [s|%X|] (305441741 :: Int32) @?= "1234ABCD"
-    it "test-case #194" $ [s|%X|] (3989525555 :: Word32) @?= "EDCB5433"
-    it "test-case #195" $ [s|%c|] 'x' @?= "x"
-    it "test-case #196" $ [s|%%|] @?= "%"
-    it "test-case #197" $ [s|%+s|] "Hallo heimur" @?= "Hallo heimur"
-    it "test-case #198" $ [s|%+d|] (1024 :: Int32) @?= "+1024"
-    it "test-case #199" $ [s|%+d|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #200" $ [s|%+i|] (1024 :: Int32) @?= "+1024"
-    it "test-case #201" $ [s|%+i|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #202" $ [s|%+u|] (1024 :: Int32) @?= "1024"
-    it "test-case #203" $ [s|%+u|] (4294966272 :: Word32) @?= "4294966272"
-    it "test-case #204" $ [s|%+o|] (511 :: Int32) @?= "777"
-    it "test-case #205" $ [s|%+o|] (4294966785 :: Word32) @?= "37777777001"
-    it "test-case #206" $ [s|%+x|] (305441741 :: Int32) @?= "1234abcd"
-    it "test-case #207" $ [s|%+x|] (3989525555 :: Word32) @?= "edcb5433"
-    it "test-case #208" $ [s|%+X|] (305441741 :: Int32) @?= "1234ABCD"
-    it "test-case #209" $ [s|%+X|] (3989525555 :: Word32) @?= "EDCB5433"
-    it "test-case #210" $ [s|%+c|] 'x' @?= "x"
-    it "test-case #211" $ [s|% s|] "Hallo heimur" @?= "Hallo heimur"
-    it "test-case #212" $ [s|% d|] (1024 :: Int32) @?= " 1024"
-    it "test-case #213" $ [s|% d|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #214" $ [s|% i|] (1024 :: Int32) @?= " 1024"
-    it "test-case #215" $ [s|% i|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #216" $ [s|% u|] (1024 :: Int32) @?= "1024"
-    it "test-case #217" $ [s|% u|] (4294966272 :: Word32) @?= "4294966272"
-    it "test-case #218" $ [s|% o|] (511 :: Int32) @?= "777"
-    it "test-case #219" $ [s|% o|] (4294966785 :: Word32) @?= "37777777001"
-    it "test-case #220" $ [s|% x|] (305441741 :: Int32) @?= "1234abcd"
-    it "test-case #221" $ [s|% x|] (3989525555 :: Word32) @?= "edcb5433"
-    it "test-case #222" $ [s|% X|] (305441741 :: Int32) @?= "1234ABCD"
-    it "test-case #223" $ [s|% X|] (3989525555 :: Word32) @?= "EDCB5433"
-    it "test-case #224" $ [s|% c|] 'x' @?= "x"
-    it "test-case #225" $ [s|%+ s|] "Hallo heimur" @?= "Hallo heimur"
-    it "test-case #226" $ [s|%+ d|] (1024 :: Int32) @?= "+1024"
-    it "test-case #227" $ [s|%+ d|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #228" $ [s|%+ i|] (1024 :: Int32) @?= "+1024"
-    it "test-case #229" $ [s|%+ i|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #230" $ [s|%+ u|] (1024 :: Int32) @?= "1024"
-    it "test-case #231" $ [s|%+ u|] (4294966272 :: Word32) @?= "4294966272"
-    it "test-case #232" $ [s|%+ o|] (511 :: Int32) @?= "777"
-    it "test-case #233" $ [s|%+ o|] (4294966785 :: Word32) @?= "37777777001"
-    it "test-case #234" $ [s|%+ x|] (305441741 :: Int32) @?= "1234abcd"
-    it "test-case #235" $ [s|%+ x|] (3989525555 :: Word32) @?= "edcb5433"
-    it "test-case #236" $ [s|%+ X|] (305441741 :: Int32) @?= "1234ABCD"
-    it "test-case #237" $ [s|%+ X|] (3989525555 :: Word32) @?= "EDCB5433"
-    it "test-case #238" $ [s|%+ c|] 'x' @?= "x"
-    it "test-case #239" $ [s|%#o|] (511 :: Int32) @?= "0777"
-    it "test-case #240" $ [s|%#o|] (4294966785 :: Word32) @?= "037777777001"
-    it "test-case #241" $ [s|%#x|] (305441741 :: Int32) @?= "0x1234abcd"
-    it "test-case #242" $ [s|%#x|] (3989525555 :: Word32) @?= "0xedcb5433"
-    it "test-case #243" $ [s|%#X|] (305441741 :: Int32) @?= "0X1234ABCD"
-    it "test-case #244" $ [s|%#X|] (3989525555 :: Word32) @?= "0XEDCB5433"
-    it "test-case #245" $ [s|%#o|] (0 :: Word32) @?= "0"
-    it "test-case #246" $ [s|%#x|] (0 :: Word32) @?= "0"
-    it "test-case #247" $ [s|%#X|] (0 :: Word32) @?= "0"
-    it "test-case #248" $ [s|%1s|] "Hallo heimur" @?= "Hallo heimur"
-    it "test-case #249" $ [s|%1d|] (1024 :: Int32) @?= "1024"
-    it "test-case #250" $ [s|%1d|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #251" $ [s|%1i|] (1024 :: Int32) @?= "1024"
-    it "test-case #252" $ [s|%1i|] (-1024 :: Int32) @?= "-1024"
-    it "test-case #253" $ [s|%1u|] (1024 :: Int32) @?= "1024"
-    it "test-case #254" $ [s|%1u|] (4294966272 :: Word32) @?= "4294966272"
-    it "test-case #255" $ [s|%1o|] (511 :: Int32) @?= "777"
-    it "test-case #256" $ [s|%1o|] (4294966785 :: Word32) @?= "37777777001"
-    it "test-case #257" $ [s|%1x|] (305441741 :: Int32) @?= "1234abcd"
-    it "test-case #258" $ [s|%1x|] (3989525555 :: Word32) @?= "edcb5433"
-    it "test-case #259" $ [s|%1X|] (305441741 :: Int32) @?= "1234ABCD"
-    it "test-case #260" $ [s|%1X|] (3989525555 :: Word32) @?= "EDCB5433"
-    it "test-case #261" $ [s|%1c|] 'x' @?= "x"
-    it "test-case #262" $ [s|%20s|] "Hallo" @?= "               Hallo"
-    it "test-case #263" $ [s|%20d|] (1024 :: Int32) @?= "                1024"
-    it "test-case #264" $ [s|%20d|] (-1024 :: Int32) @?= "               -1024"
-    it "test-case #265" $ [s|%20i|] (1024 :: Int32) @?= "                1024"
-    it "test-case #266" $ [s|%20i|] (-1024 :: Int32) @?= "               -1024"
-    it "test-case #267" $ [s|%20u|] (1024 :: Int32) @?= "                1024"
-    it "test-case #268" $ [s|%20u|] (4294966272 :: Word32) @?= "          4294966272"
-    it "test-case #269" $ [s|%20o|] (511 :: Int32) @?= "                 777"
-    it "test-case #270" $ [s|%20o|] (4294966785 :: Word32) @?= "         37777777001"
-    it "test-case #271" $ [s|%20x|] (305441741 :: Int32) @?= "            1234abcd"
-    it "test-case #272" $ [s|%20x|] (3989525555 :: Word32) @?= "            edcb5433"
-    it "test-case #273" $ [s|%20X|] (305441741 :: Int32) @?= "            1234ABCD"
-    it "test-case #274" $ [s|%20X|] (3989525555 :: Word32) @?= "            EDCB5433"
-    it "test-case #275" $ [s|%20c|] 'x' @?= "                   x"
-    it "test-case #276" $ [s|%-20s|] "Hallo" @?= "Hallo               "
-    it "test-case #277" $ [s|%-20d|] (1024 :: Int32) @?= "1024                "
-    it "test-case #278" $ [s|%-20d|] (-1024 :: Int32) @?= "-1024               "
-    it "test-case #279" $ [s|%-20i|] (1024 :: Int32) @?= "1024                "
-    it "test-case #280" $ [s|%-20i|] (-1024 :: Int32) @?= "-1024               "
-    it "test-case #281" $ [s|%-20u|] (1024 :: Int32) @?= "1024                "
-    it "test-case #282" $ [s|%-20u|] (4294966272 :: Word32) @?= "4294966272          "
-    it "test-case #283" $ [s|%-20o|] (511 :: Int32) @?= "777                 "
-    it "test-case #284" $ [s|%-20o|] (4294966785 :: Word32) @?= "37777777001         "
-    it "test-case #285" $ [s|%-20x|] (305441741 :: Int32) @?= "1234abcd            "
-    it "test-case #286" $ [s|%-20x|] (3989525555 :: Word32) @?= "edcb5433            "
-    it "test-case #287" $ [s|%-20X|] (305441741 :: Int32) @?= "1234ABCD            "
-    it "test-case #288" $ [s|%-20X|] (3989525555 :: Word32) @?= "EDCB5433            "
-    it "test-case #289" $ [s|%-20c|] 'x' @?= "x                   "
-    it "test-case #290" $ [s|%020d|] (1024 :: Int32) @?= "00000000000000001024"
-    it "test-case #291" $ [s|%020d|] (-1024 :: Int32) @?= "-0000000000000001024"
-    it "test-case #292" $ [s|%020i|] (1024 :: Int32) @?= "00000000000000001024"
-    it "test-case #293" $ [s|%020i|] (-1024 :: Int32) @?= "-0000000000000001024"
-    it "test-case #294" $ [s|%020u|] (1024 :: Int32) @?= "00000000000000001024"
-    it "test-case #295" $ [s|%020u|] (4294966272 :: Word32) @?= "00000000004294966272"
-    it "test-case #296" $ [s|%020o|] (511 :: Int32) @?= "00000000000000000777"
-    it "test-case #297" $ [s|%020o|] (4294966785 :: Word32) @?= "00000000037777777001"
-    it "test-case #298" $ [s|%020x|] (305441741 :: Int32) @?= "0000000000001234abcd"
-    it "test-case #299" $ [s|%020x|] (3989525555 :: Word32) @?= "000000000000edcb5433"
-    it "test-case #300" $ [s|%020X|] (305441741 :: Int32) @?= "0000000000001234ABCD"
-    it "test-case #301" $ [s|%020X|] (3989525555 :: Word32) @?= "000000000000EDCB5433"
-    it "test-case #302" $ [s|%#20o|] (511 :: Int32) @?= "                0777"
-    it "test-case #303" $ [s|%#20o|] (4294966785 :: Word32) @?= "        037777777001"
-    it "test-case #304" $ [s|%#20x|] (305441741 :: Int32) @?= "          0x1234abcd"
-    it "test-case #305" $ [s|%#20x|] (3989525555 :: Word32) @?= "          0xedcb5433"
-    it "test-case #306" $ [s|%#20X|] (305441741 :: Int32) @?= "          0X1234ABCD"
-    it "test-case #307" $ [s|%#20X|] (3989525555 :: Word32) @?= "          0XEDCB5433"
-    it "test-case #308" $ [s|%#020o|] (511 :: Int32) @?= "00000000000000000777"
-    it "test-case #309" $ [s|%#020o|] (4294966785 :: Word32) @?= "00000000037777777001"
-    it "test-case #310" $ [s|%#020x|] (305441741 :: Int32) @?= "0x00000000001234abcd"
-    it "test-case #311" $ [s|%#020x|] (3989525555 :: Word32) @?= "0x0000000000edcb5433"
-    it "test-case #312" $ [s|%#020X|] (305441741 :: Int32) @?= "0X00000000001234ABCD"
-    it "test-case #313" $ [s|%#020X|] (3989525555 :: Word32) @?= "0X0000000000EDCB5433"
-    it "test-case #314" $ [s|%0-20s|] "Hallo" @?= "Hallo               "
-    it "test-case #315" $ [s|%0-20d|] (1024 :: Int32) @?= "1024                "
-    it "test-case #316" $ [s|%0-20d|] (-1024 :: Int32) @?= "-1024               "
-    it "test-case #317" $ [s|%0-20i|] (1024 :: Int32) @?= "1024                "
-    it "test-case #318" $ [s|%0-20i|] (-1024 :: Int32) @?= "-1024               "
-    it "test-case #319" $ [s|%0-20u|] (1024 :: Int32) @?= "1024                "
-    it "test-case #320" $ [s|%0-20u|] (4294966272 :: Word32) @?= "4294966272          "
-    it "test-case #321" $ [s|%-020o|] (511 :: Int32) @?= "777                 "
-    it "test-case #322" $ [s|%-020o|] (4294966785 :: Word32) @?= "37777777001         "
-    it "test-case #323" $ [s|%-020x|] (305441741 :: Int32) @?= "1234abcd            "
-    it "test-case #324" $ [s|%-020x|] (3989525555 :: Word32) @?= "edcb5433            "
-    it "test-case #325" $ [s|%-020X|] (305441741 :: Int32) @?= "1234ABCD            "
-    it "test-case #326" $ [s|%-020X|] (3989525555 :: Word32) @?= "EDCB5433            "
-    it "test-case #327" $ [s|%-020c|] 'x' @?= "x                   "
-    it "test-case #328" $ [s|%*s|] (20 :: Int32) "Hallo" @?= "               Hallo"
-    it "test-case #329" $
-        [s|%*d|] (20 :: Int32) (1024 :: Int32) @?= "                1024"
-    it "test-case #330" $
-        [s|%*d|] (20 :: Int32) (-1024 :: Int32) @?= "               -1024"
-    it "test-case #331" $
-        [s|%*i|] (20 :: Int32) (1024 :: Int32) @?= "                1024"
-    it "test-case #332" $
-        [s|%*i|] (20 :: Int32) (-1024 :: Int32) @?= "               -1024"
-    it "test-case #333" $
-        [s|%*u|] (20 :: Int32) (1024 :: Int32) @?= "                1024"
-    it "test-case #334" $
-        [s|%*u|] (20 :: Int32) (4294966272 :: Word32) @?= "          4294966272"
-    it "test-case #335" $ [s|%*o|] (20 :: Int32) (511 :: Int32) @?= "                 777"
-    it "test-case #336" $
-        [s|%*o|] (20 :: Int32) (4294966785 :: Word32) @?= "         37777777001"
-    it "test-case #337" $
-        [s|%*x|] (20 :: Int32) (305441741 :: Int32) @?= "            1234abcd"
-    it "test-case #338" $
-        [s|%*x|] (20 :: Int32) (3989525555 :: Word32) @?= "            edcb5433"
-    it "test-case #339" $
-        [s|%*X|] (20 :: Int32) (305441741 :: Int32) @?= "            1234ABCD"
-    it "test-case #340" $
-        [s|%*X|] (20 :: Int32) (3989525555 :: Word32) @?= "            EDCB5433"
-    it "test-case #341" $ [s|%*c|] (20 :: Int32) 'x' @?= "                   x"
-    it "test-case #342" $ [s|%.20s|] "Hallo heimur" @?= "Hallo heimur"
-    it "test-case #343" $ [s|%.20d|] (1024 :: Int32) @?= "00000000000000001024"
-    it "test-case #344" $ [s|%.20d|] (-1024 :: Int32) @?= "-00000000000000001024"
-    it "test-case #345" $ [s|%.20i|] (1024 :: Int32) @?= "00000000000000001024"
-    it "test-case #346" $ [s|%.20i|] (-1024 :: Int32) @?= "-00000000000000001024"
-    it "test-case #347" $ [s|%.20u|] (1024 :: Int32) @?= "00000000000000001024"
-    it "test-case #348" $ [s|%.20u|] (4294966272 :: Word32) @?= "00000000004294966272"
-    it "test-case #349" $ [s|%.20o|] (511 :: Int32) @?= "00000000000000000777"
-    it "test-case #350" $ [s|%.20o|] (4294966785 :: Word32) @?= "00000000037777777001"
-    it "test-case #351" $ [s|%.20x|] (305441741 :: Int32) @?= "0000000000001234abcd"
-    it "test-case #352" $ [s|%.20x|] (3989525555 :: Word32) @?= "000000000000edcb5433"
-    it "test-case #353" $ [s|%.20X|] (305441741 :: Int32) @?= "0000000000001234ABCD"
-    it "test-case #354" $ [s|%.20X|] (3989525555 :: Word32) @?= "000000000000EDCB5433"
-    it "test-case #355" $ [s|%20.5s|] "Hallo heimur" @?= "               Hallo"
-    it "test-case #356" $ [s|%20.5d|] (1024 :: Int32) @?= "               01024"
-    it "test-case #357" $ [s|%20.5d|] (-1024 :: Int32) @?= "              -01024"
-    it "test-case #358" $ [s|%20.5i|] (1024 :: Int32) @?= "               01024"
-    it "test-case #359" $ [s|%20.5i|] (-1024 :: Int32) @?= "              -01024"
-    it "test-case #360" $ [s|%20.5u|] (1024 :: Int32) @?= "               01024"
-    it "test-case #361" $ [s|%20.5u|] (4294966272 :: Word32) @?= "          4294966272"
-    it "test-case #362" $ [s|%20.5o|] (511 :: Int32) @?= "               00777"
-    it "test-case #363" $ [s|%20.5o|] (4294966785 :: Word32) @?= "         37777777001"
-    it "test-case #364" $ [s|%20.5x|] (305441741 :: Int32) @?= "            1234abcd"
-    it "test-case #365" $ [s|%20.10x|] (3989525555 :: Word32) @?= "          00edcb5433"
-    it "test-case #366" $ [s|%20.5X|] (305441741 :: Int32) @?= "            1234ABCD"
-    it "test-case #367" $ [s|%20.10X|] (3989525555 :: Word32) @?= "          00EDCB5433"
-    it "test-case #369" $ [s|%020.5d|] (1024 :: Int32) @?= "               01024"
-    it "test-case #370" $ [s|%020.5d|] (-1024 :: Int32) @?= "              -01024"
-    it "test-case #371" $ [s|%020.5i|] (1024 :: Int32) @?= "               01024"
-    it "test-case #372" $ [s|%020.5i|] (-1024 :: Int32) @?= "              -01024"
-    it "test-case #373" $ [s|%020.5u|] (1024 :: Int32) @?= "               01024"
-    it "test-case #374" $ [s|%020.5u|] (4294966272 :: Word32) @?= "          4294966272"
-    it "test-case #375" $ [s|%020.5o|] (511 :: Int32) @?= "               00777"
-    it "test-case #376" $ [s|%020.5o|] (4294966785 :: Word32) @?= "         37777777001"
-    it "test-case #377" $ [s|%020.5x|] (305441741 :: Int32) @?= "            1234abcd"
-    it "test-case #378" $ [s|%020.10x|] (3989525555 :: Word32) @?= "          00edcb5433"
-    it "test-case #379" $ [s|%020.5X|] (305441741 :: Int32) @?= "            1234ABCD"
-    it "test-case #380" $ [s|%020.10X|] (3989525555 :: Word32) @?= "          00EDCB5433"
-    it "test-case #381" $ [s|%.0s|] "Hallo heimur" @?= ""
-    it "test-case #382" $ [s|%20.0s|] "Hallo heimur" @?= "                    "
-    it "test-case #383" $ [s|%.s|] "Hallo heimur" @?= ""
-    it "test-case #384" $ [s|%20.s|] "Hallo heimur" @?= "                    "
-    it "test-case #385" $ [s|%20.0d|] (1024 :: Int32) @?= "                1024"
-    it "test-case #386" $ [s|%20.d|] (-1024 :: Int32) @?= "               -1024"
-    it "test-case #387" $ [s|%20.d|] (0 :: Int32) @?= "                    "
-    it "test-case #388" $ [s|%20.0i|] (1024 :: Int32) @?= "                1024"
-    it "test-case #389" $ [s|%20.i|] (-1024 :: Int32) @?= "               -1024"
-    it "test-case #390" $ [s|%20.i|] (0 :: Int32) @?= "                    "
-    it "test-case #391" $ [s|%20.u|] (1024 :: Int32) @?= "                1024"
-    it "test-case #392" $ [s|%20.0u|] (4294966272 :: Word32) @?= "          4294966272"
-    it "test-case #393" $ [s|%20.u|] (0 :: Word32) @?= "                    "
-    it "test-case #394" $ [s|%20.o|] (511 :: Int32) @?= "                 777"
-    it "test-case #395" $ [s|%20.0o|] (4294966785 :: Word32) @?= "         37777777001"
-    it "test-case #396" $ [s|%20.o|] (0 :: Word32) @?= "                    "
-    it "test-case #397" $ [s|%20.x|] (305441741 :: Int32) @?= "            1234abcd"
-    it "test-case #398" $ [s|%20.0x|] (3989525555 :: Word32) @?= "            edcb5433"
-    it "test-case #399" $ [s|%20.x|] (0 :: Word32) @?= "                    "
-    it "test-case #400" $ [s|%20.X|] (305441741 :: Int32) @?= "            1234ABCD"
-    it "test-case #401" $ [s|%20.0X|] (3989525555 :: Word32) @?= "            EDCB5433"
-    it "test-case #402" $ [s|%20.X|] (0 :: Word32) @?= "                    "
-    it "test-case #403" $
-        [s|% -0+*.*s|] (20 :: Int32) (5 :: Int32) "Hallo heimur" @?=
-        "Hallo               "
-    it "test-case #404" $
-        [s|% -0+*.*d|] (20 :: Int32) (5 :: Int32) (1024 :: Int32) @?=
-        "+01024              "
-    it "test-case #405" $
-        [s|% -0+*.*d|] (20 :: Int32) (5 :: Int32) (-1024 :: Int32) @?=
-        "-01024              "
-    it "test-case #406" $
-        [s|% -0+*.*i|] (20 :: Int32) (5 :: Int32) (1024 :: Int32) @?=
-        "+01024              "
-    it "test-case #407" $
-        [s|% 0-+*.*i|] (20 :: Int32) (5 :: Int32) (-1024 :: Int32) @?=
-        "-01024              "
-    it "test-case #408" $
-        [s|% 0-+*.*u|] (20 :: Int32) (5 :: Int32) (1024 :: Int32) @?=
-        "01024               "
-    it "test-case #409" $
-        [s|% 0-+*.*u|] (20 :: Int32) (5 :: Int32) (4294966272 :: Word32) @?=
-        "4294966272          "
-    it "test-case #410" $
-        [s|%+ -0*.*o|] (20 :: Int32) (5 :: Int32) (511 :: Int32) @?=
-        "00777               "
-    it "test-case #411" $
-        [s|%+ -0*.*o|] (20 :: Int32) (5 :: Int32) (4294966785 :: Word32) @?=
-        "37777777001         "
-    it "test-case #412" $
-        [s|%+ -0*.*x|] (20 :: Int32) (5 :: Int32) (305441741 :: Int32) @?=
-        "1234abcd            "
-    it "test-case #413" $
-        [s|%+ -0*.*x|] (20 :: Int32) (10 :: Int32) (3989525555 :: Word32) @?=
-        "00edcb5433          "
-    it "test-case #414" $
-        [s|% -+0*.*X|] (20 :: Int32) (5 :: Int32) (305441741 :: Int32) @?=
-        "1234ABCD            "
-    it "test-case #415" $
-        [s|% -+0*.*X|] (20 :: Int32) (10 :: Int32) (3989525555 :: Word32) @?=
-        "00EDCB5433          "
-    it "test-case #416" $ [s|%*sx|] (-3 :: Int32) "hi" @?= "hi x"
diff --git a/tests/format.hs b/tests/format.hs
deleted file mode 100644
--- a/tests/format.hs
+++ /dev/null
@@ -1,42 +0,0 @@
-{-# OPTIONS_GHC -Wwarn #-}
-{-# LANGUAGE QuasiQuotes #-}
-
-module Main where
-
-import Foreign.Ptr
-import GeneratedSpec
-import Language.Haskell.Printf
-import Test.HUnit
-import Test.Hspec
-
-main :: IO ()
-main =
-    hspec $
-    describe "th-printf" $ do
-        GeneratedSpec.spec
-        it "hexadecimal float" $ do
-            [s|%a|] 0.857421875 @?= "0x1.b7p-1"
-            [s|%A|] 3.1415926 @?= "0X1.921FB4D12D84AP+1"
-            [s|%.3a|] 1.999999999 @?= "0x2.000p+0"
-            [s|%.0a|] 1.999999999 @?= "0x2p+0"
-            [s|%#.0a|] 1.999999999 @?= "0x2.p+0"
-            [s|%.3a|] 0.7576 @?= "0x1.83ep-1"
-            [s|%015.3a|] 0.7576 @?= "0x000001.83ep-1"
-            [s|% 15.3a|] 0.7576 @?= "     0x1.83ep-1"
-        it "Show instances" $ do
-            [s|%?|] () @?= "()"
-            [s|%10?|] () @?= "        ()"
-        it "pointer" $ do
-            [s|%p|] nullPtr @?= "0x0"
-            [s|%15p|] fakePtr @?= "     0xdeadbeef"
-            -- sign flag does nothing
-            [s|%+p|] fakePtr @?= "0xdeadbeef"
-            -- prefix flag does nothing
-            [s|%#p|] fakePtr @?= "0xdeadbeef"
-            -- zero flag does nothing
-            [s|%015p|] fakePtr @?= "     0xdeadbeef"
-            -- left-pad flag does nothing
-            [s|%-15p|] fakePtr @?= "     0xdeadbeef"
-
-fakePtr :: Ptr ()
-fakePtr = nullPtr `plusPtr` 0xdeadbeef
diff --git a/tests/printf/GeneratedSpec.hs b/tests/printf/GeneratedSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/printf/GeneratedSpec.hs
@@ -0,0 +1,450 @@
+-- This file contains autogenerated erroneous printf strings, which th-printf
+-- will warn about, but they shouldn't break a -Werror build
+{-# OPTIONS_GHC -Wwarn #-}
+{-# LANGUAGE QuasiQuotes #-}
+
+module GeneratedSpec where
+
+import Data.Int
+import Data.Word
+import Language.Haskell.Printf
+import Test.HUnit
+import Test.Hspec
+
+spec :: Spec
+spec = do
+    it "test-case #1" $ [s|%.*f|] (2 :: Int32) (0.33333333 :: Double) @?= "0.33"
+    it "test-case #2" $ [s|%.3s|] "foobar" @?= "foo"
+    it "test-case #3" $ [s|%10.5d|] (4 :: Int32) @?= "     00004"
+    it "test-case #4" $ [s|% d|] (42 :: Int32) @?= " 42"
+    it "test-case #5" $ [s|% d|] (-42 :: Int32) @?= "-42"
+    it "test-case #6" $ [s|% 5d|] (42 :: Int32) @?= "   42"
+    it "test-case #7" $ [s|% 5d|] (-42 :: Int32) @?= "  -42"
+    it "test-case #8" $ [s|% 15d|] (42 :: Int32) @?= "             42"
+    it "test-case #9" $ [s|% 15d|] (-42 :: Int32) @?= "            -42"
+    it "test-case #10" $ [s|%+d|] (42 :: Int32) @?= "+42"
+    it "test-case #11" $ [s|%+d|] (-42 :: Int32) @?= "-42"
+    it "test-case #12" $ [s|%+5d|] (42 :: Int32) @?= "  +42"
+    it "test-case #13" $ [s|%+5d|] (-42 :: Int32) @?= "  -42"
+    it "test-case #14" $ [s|%+15d|] (42 :: Int32) @?= "            +42"
+    it "test-case #15" $ [s|%+15d|] (-42 :: Int32) @?= "            -42"
+    it "test-case #16" $ [s|%0d|] (42 :: Int32) @?= "42"
+    it "test-case #17" $ [s|%0d|] (-42 :: Int32) @?= "-42"
+    it "test-case #18" $ [s|%05d|] (42 :: Int32) @?= "00042"
+    it "test-case #19" $ [s|%05d|] (-42 :: Int32) @?= "-0042"
+    it "test-case #20" $ [s|%015d|] (42 :: Int32) @?= "000000000000042"
+    it "test-case #21" $ [s|%015d|] (-42 :: Int32) @?= "-00000000000042"
+    it "test-case #22" $ [s|%-d|] (42 :: Int32) @?= "42"
+    it "test-case #23" $ [s|%-d|] (-42 :: Int32) @?= "-42"
+    it "test-case #24" $ [s|%-5d|] (42 :: Int32) @?= "42   "
+    it "test-case #25" $ [s|%-5d|] (-42 :: Int32) @?= "-42  "
+    it "test-case #26" $ [s|%-15d|] (42 :: Int32) @?= "42             "
+    it "test-case #27" $ [s|%-15d|] (-42 :: Int32) @?= "-42            "
+    it "test-case #28" $ [s|%-0d|] (42 :: Int32) @?= "42"
+    it "test-case #29" $ [s|%-0d|] (-42 :: Int32) @?= "-42"
+    it "test-case #30" $ [s|%-05d|] (42 :: Int32) @?= "42   "
+    it "test-case #31" $ [s|%-05d|] (-42 :: Int32) @?= "-42  "
+    it "test-case #32" $ [s|%-015d|] (42 :: Int32) @?= "42             "
+    it "test-case #33" $ [s|%-015d|] (-42 :: Int32) @?= "-42            "
+    it "test-case #34" $ [s|%0-d|] (42 :: Int32) @?= "42"
+    it "test-case #35" $ [s|%0-d|] (-42 :: Int32) @?= "-42"
+    it "test-case #36" $ [s|%0-5d|] (42 :: Int32) @?= "42   "
+    it "test-case #37" $ [s|%0-5d|] (-42 :: Int32) @?= "-42  "
+    it "test-case #38" $ [s|%0-15d|] (42 :: Int32) @?= "42             "
+    it "test-case #39" $ [s|%0-15d|] (-42 :: Int32) @?= "-42            "
+    it "test-case #43" $ [s|%.2f|] (42.8952 :: Double) @?= "42.90"
+    it "test-case #44" $ [s|%.2F|] (42.8952 :: Double) @?= "42.90"
+    it "test-case #45" $ [s|%.10f|] (42.8952 :: Double) @?= "42.8952000000"
+    it "test-case #46" $ [s|%1.2f|] (42.8952 :: Double) @?= "42.90"
+    it "test-case #47" $ [s|%6.2f|] (42.8952 :: Double) @?= " 42.90"
+    it "test-case #49" $ [s|%+6.2f|] (42.8952 :: Double) @?= "+42.90"
+    it "test-case #50" $ [s|%5.10f|] (42.8952 :: Double) @?= "42.8952000000"
+  -- 51: anti-test
+  -- 52: anti-test
+  -- 53: excluded for Haskell
+  -- 55: excluded for Haskell
+  -- 56: excluded for Haskell
+  -- 58: excluded for Haskell
+  -- 59: excluded for Haskell
+    it "test-case #60" $ [s|%*s|] (4 :: Int32) "foo" @?= " foo"
+    it "test-case #61" $
+        [s|%*.*f|] (10 :: Int32) (2 :: Int32) (3.14159265 :: Double) @?= "      3.14"
+    it "test-case #63" $
+        [s|%-*.*f|] (10 :: Int32) (2 :: Int32) (3.14159265 :: Double) @?= "3.14      "
+  -- 64: anti-test
+  -- 65: anti-test
+    it "test-case #66" $ [s|+%s+|] "hello" @?= "+hello+"
+    it "test-case #67" $ [s|+%d+|] (10 :: Int32) @?= "+10+"
+    it "test-case #68" $ [s|%c|] 'a' @?= "a"
+  -- unsound test: it "test-case #69" $ [s|%c|] (32 :: Int32) @?= " "
+  -- unsound test: it "test-case #70" $ [s|%c|] (36 :: Int32) @?= "$"
+    it "test-case #71" $ [s|%d|] (10 :: Int32) @?= "10"
+  -- 72: anti-test
+  -- 73: anti-test
+  -- 74: excluded for Haskell
+  -- 75: excluded for Haskell
+    it "test-case #76" $
+        [s|%+#22.15e|] (7.89456123e8 :: Double) @?= "+7.894561230000000e+08"
+    it "test-case #77" $
+        [s|%-#22.15e|] (7.89456123e8 :: Double) @?= "7.894561230000000e+08 "
+    it "test-case #78" $
+        [s|%#22.15e|] (7.89456123e8 :: Double) @?= " 7.894561230000000e+08"
+    it "test-case #79" $ [s|%#1.1g|] (7.89456123e8 :: Double) @?= "8.e+08"
+    it "test-case #81" $ [s|%+8lld|] (100 :: Int64) @?= "    +100"
+    it "test-case #82" $ [s|%+.8lld|] (100 :: Int64) @?= "+00000100"
+    it "test-case #83" $ [s|%+10.8lld|] (100 :: Int64) @?= " +00000100"
+  -- 84: excluded for Haskell
+    it "test-case #85" $ [s|%-1.5lld|] (-100 :: Int64) @?= "-00100"
+    it "test-case #86" $ [s|%5lld|] (100 :: Int64) @?= "  100"
+    it "test-case #87" $ [s|%5lld|] (-100 :: Int64) @?= " -100"
+    it "test-case #88" $ [s|%-5lld|] (100 :: Int64) @?= "100  "
+    it "test-case #89" $ [s|%-5lld|] (-100 :: Int64) @?= "-100 "
+    it "test-case #90" $ [s|%-.5lld|] (100 :: Int64) @?= "00100"
+    it "test-case #91" $ [s|%-.5lld|] (-100 :: Int64) @?= "-00100"
+    it "test-case #92" $ [s|%-8.5lld|] (100 :: Int64) @?= "00100   "
+    it "test-case #93" $ [s|%-8.5lld|] (-100 :: Int64) @?= "-00100  "
+    it "test-case #94" $ [s|%05lld|] (100 :: Int64) @?= "00100"
+    it "test-case #95" $ [s|%05lld|] (-100 :: Int64) @?= "-0100"
+    it "test-case #96" $ [s|% lld|] (100 :: Int64) @?= " 100"
+    it "test-case #97" $ [s|% lld|] (-100 :: Int64) @?= "-100"
+    it "test-case #98" $ [s|% 5lld|] (100 :: Int64) @?= "  100"
+    it "test-case #99" $ [s|% 5lld|] (-100 :: Int64) @?= " -100"
+    it "test-case #100" $ [s|% .5lld|] (100 :: Int64) @?= " 00100"
+    it "test-case #101" $ [s|% .5lld|] (-100 :: Int64) @?= "-00100"
+    it "test-case #102" $ [s|% 8.5lld|] (100 :: Int64) @?= "   00100"
+    it "test-case #103" $ [s|% 8.5lld|] (-100 :: Int64) @?= "  -00100"
+    it "test-case #104" $ [s|%.0lld|] (0 :: Int64) @?= ""
+    it "test-case #105" $ [s|%#+21.18llx|] (-100 :: Int64) @?= " 0x00ffffffffffffff9c"
+    it "test-case #106" $ [s|%#.25llo|] (-100 :: Int64) @?= "0001777777777777777777634"
+    it "test-case #107" $ [s|%#+24.20llo|] (-100 :: Int64) @?= " 01777777777777777777634"
+    it "test-case #108" $ [s|%#+18.21llX|] (-100 :: Int64) @?= "0X00000FFFFFFFFFFFFFF9C"
+    it "test-case #109" $ [s|%#+20.24llo|] (-100 :: Int64) @?= "001777777777777777777634"
+    it "test-case #110" $ [s|%#+25.22llu|] (-1 :: Int64) @?= "   0018446744073709551615"
+    it "test-case #111" $ [s|%#+25.22llu|] (-1 :: Int64) @?= "   0018446744073709551615"
+    it "test-case #112" $
+        [s|%#+30.25llu|] (-1 :: Int64) @?= "     0000018446744073709551615"
+    it "test-case #113" $ [s|%+#25.22lld|] (-1 :: Int64) @?= "  -0000000000000000000001"
+    it "test-case #114" $ [s|%#-8.5llo|] (100 :: Int64) @?= "00144   "
+    it "test-case #115" $ [s|%#-+ 08.5lld|] (100 :: Int64) @?= "+00100  "
+    it "test-case #116" $ [s|%#-+ 08.5lld|] (100 :: Int64) @?= "+00100  "
+    it "test-case #117" $
+        [s|%.40lld|] (1 :: Int64) @?= "0000000000000000000000000000000000000001"
+    it "test-case #118" $
+        [s|% .40lld|] (1 :: Int64) @?= " 0000000000000000000000000000000000000001"
+    it "test-case #119" $
+        [s|% .40d|] (1 :: Int32) @?= " 0000000000000000000000000000000000000001"
+  -- 121: excluded for Haskell
+  -- 124: excluded for Haskell
+    it "test-case #125" $ [s|% d|] (1 :: Int32) @?= " 1"
+    it "test-case #126" $ [s|%+ d|] (1 :: Int32) @?= "+1"
+    it "test-case #129" $ [s|%#012x|] (1 :: Int32) @?= "0x0000000001"
+    it "test-case #130" $ [s|%#04.8x|] (1 :: Int32) @?= "0x00000001"
+    it "test-case #131" $ [s|%#-08.2x|] (1 :: Int32) @?= "0x01    "
+    it "test-case #132" $ [s|%#08o|] (1 :: Int32) @?= "00000001"
+  -- 133: excluded for Haskell
+  -- 137: excluded for Haskell
+    it "test-case #142" $ [s|%.1s|] "foo" @?= "f"
+    it "test-case #143" $ [s|%.*s|] (1 :: Int32) "foo" @?= "f"
+    it "test-case #144" $ [s|%*s|] (-5 :: Int32) "foo" @?= "foo  "
+    it "test-case #145" $ [s|hello|] @?= "hello"
+  -- 147: excluded for Haskell
+    it "test-case #148" $ [s|%3c|] 'a' @?= "  a"
+    it "test-case #149" $ [s|%3d|] (1234 :: Int32) @?= "1234"
+  -- 150: excluded for Haskell
+    it "test-case #152" $ [s|%-1d|] (2 :: Int32) @?= "2"
+    it "test-case #153" $ [s|%2.4f|] (8.6 :: Double) @?= "8.6000"
+    it "test-case #154" $ [s|%0f|] (0.6 :: Double) @?= "0.600000"
+    it "test-case #155" $ [s|%.0f|] (0.6 :: Double) @?= "1"
+    it "test-case #156" $ [s|%2.4e|] (8.6 :: Double) @?= "8.6000e+00"
+    it "test-case #157" $ [s|% 2.4e|] (8.6 :: Double) @?= " 8.6000e+00"
+    it "test-case #159" $ [s|% 2.4e|] (-8.6 :: Double) @?= "-8.6000e+00"
+    it "test-case #160" $ [s|%+2.4e|] (8.6 :: Double) @?= "+8.6000e+00"
+    it "test-case #161" $ [s|%2.4g|] (8.6 :: Double) @?= "8.6"
+    it "test-case #162" $ [s|%-i|] (-1 :: Int32) @?= "-1"
+    it "test-case #163" $ [s|%-i|] (1 :: Int32) @?= "1"
+    it "test-case #164" $ [s|%+i|] (1 :: Int32) @?= "+1"
+    it "test-case #165" $ [s|%o|] (10 :: Int32) @?= "12"
+  -- 166: excluded for Haskell
+  -- 167: excluded for Haskell
+    it "test-case #169" $ [s|%s|] "%%%%" @?= "%%%%"
+    it "test-case #170" $ [s|%u|] (-1 :: Int32) @?= "4294967295"
+  -- 171: excluded for Haskell
+  -- 172: excluded for Haskell
+  -- 173: excluded for Haskell
+  -- 174: excluded for Haskell
+  -- 176: excluded for Haskell
+    it "test-case #177" $ [s|%%0|] @?= "%0"
+  -- 178: excluded for Haskell
+  -- unsound test: it "test-case #179" $ [s|%hhx|] 'a' @?= "61"
+    it "test-case #181" $ [s|Hallo heimur|] @?= "Hallo heimur"
+    it "test-case #182" $ [s|%s|] "Hallo heimur" @?= "Hallo heimur"
+    it "test-case #183" $ [s|%d|] (1024 :: Int32) @?= "1024"
+    it "test-case #184" $ [s|%d|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #185" $ [s|%i|] (1024 :: Int32) @?= "1024"
+    it "test-case #186" $ [s|%i|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #187" $ [s|%u|] (1024 :: Int32) @?= "1024"
+    it "test-case #188" $ [s|%u|] (4294966272 :: Word32) @?= "4294966272"
+    it "test-case #189" $ [s|%o|] (511 :: Int32) @?= "777"
+    it "test-case #190" $ [s|%o|] (4294966785 :: Word32) @?= "37777777001"
+    it "test-case #191" $ [s|%x|] (305441741 :: Int32) @?= "1234abcd"
+    it "test-case #192" $ [s|%x|] (3989525555 :: Word32) @?= "edcb5433"
+    it "test-case #193" $ [s|%X|] (305441741 :: Int32) @?= "1234ABCD"
+    it "test-case #194" $ [s|%X|] (3989525555 :: Word32) @?= "EDCB5433"
+    it "test-case #195" $ [s|%c|] 'x' @?= "x"
+    it "test-case #196" $ [s|%%|] @?= "%"
+    it "test-case #197" $ [s|%+s|] "Hallo heimur" @?= "Hallo heimur"
+    it "test-case #198" $ [s|%+d|] (1024 :: Int32) @?= "+1024"
+    it "test-case #199" $ [s|%+d|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #200" $ [s|%+i|] (1024 :: Int32) @?= "+1024"
+    it "test-case #201" $ [s|%+i|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #202" $ [s|%+u|] (1024 :: Int32) @?= "1024"
+    it "test-case #203" $ [s|%+u|] (4294966272 :: Word32) @?= "4294966272"
+    it "test-case #204" $ [s|%+o|] (511 :: Int32) @?= "777"
+    it "test-case #205" $ [s|%+o|] (4294966785 :: Word32) @?= "37777777001"
+    it "test-case #206" $ [s|%+x|] (305441741 :: Int32) @?= "1234abcd"
+    it "test-case #207" $ [s|%+x|] (3989525555 :: Word32) @?= "edcb5433"
+    it "test-case #208" $ [s|%+X|] (305441741 :: Int32) @?= "1234ABCD"
+    it "test-case #209" $ [s|%+X|] (3989525555 :: Word32) @?= "EDCB5433"
+    it "test-case #210" $ [s|%+c|] 'x' @?= "x"
+    it "test-case #211" $ [s|% s|] "Hallo heimur" @?= "Hallo heimur"
+    it "test-case #212" $ [s|% d|] (1024 :: Int32) @?= " 1024"
+    it "test-case #213" $ [s|% d|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #214" $ [s|% i|] (1024 :: Int32) @?= " 1024"
+    it "test-case #215" $ [s|% i|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #216" $ [s|% u|] (1024 :: Int32) @?= "1024"
+    it "test-case #217" $ [s|% u|] (4294966272 :: Word32) @?= "4294966272"
+    it "test-case #218" $ [s|% o|] (511 :: Int32) @?= "777"
+    it "test-case #219" $ [s|% o|] (4294966785 :: Word32) @?= "37777777001"
+    it "test-case #220" $ [s|% x|] (305441741 :: Int32) @?= "1234abcd"
+    it "test-case #221" $ [s|% x|] (3989525555 :: Word32) @?= "edcb5433"
+    it "test-case #222" $ [s|% X|] (305441741 :: Int32) @?= "1234ABCD"
+    it "test-case #223" $ [s|% X|] (3989525555 :: Word32) @?= "EDCB5433"
+    it "test-case #224" $ [s|% c|] 'x' @?= "x"
+    it "test-case #225" $ [s|%+ s|] "Hallo heimur" @?= "Hallo heimur"
+    it "test-case #226" $ [s|%+ d|] (1024 :: Int32) @?= "+1024"
+    it "test-case #227" $ [s|%+ d|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #228" $ [s|%+ i|] (1024 :: Int32) @?= "+1024"
+    it "test-case #229" $ [s|%+ i|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #230" $ [s|%+ u|] (1024 :: Int32) @?= "1024"
+    it "test-case #231" $ [s|%+ u|] (4294966272 :: Word32) @?= "4294966272"
+    it "test-case #232" $ [s|%+ o|] (511 :: Int32) @?= "777"
+    it "test-case #233" $ [s|%+ o|] (4294966785 :: Word32) @?= "37777777001"
+    it "test-case #234" $ [s|%+ x|] (305441741 :: Int32) @?= "1234abcd"
+    it "test-case #235" $ [s|%+ x|] (3989525555 :: Word32) @?= "edcb5433"
+    it "test-case #236" $ [s|%+ X|] (305441741 :: Int32) @?= "1234ABCD"
+    it "test-case #237" $ [s|%+ X|] (3989525555 :: Word32) @?= "EDCB5433"
+    it "test-case #238" $ [s|%+ c|] 'x' @?= "x"
+    it "test-case #239" $ [s|%#o|] (511 :: Int32) @?= "0777"
+    it "test-case #240" $ [s|%#o|] (4294966785 :: Word32) @?= "037777777001"
+    it "test-case #241" $ [s|%#x|] (305441741 :: Int32) @?= "0x1234abcd"
+    it "test-case #242" $ [s|%#x|] (3989525555 :: Word32) @?= "0xedcb5433"
+    it "test-case #243" $ [s|%#X|] (305441741 :: Int32) @?= "0X1234ABCD"
+    it "test-case #244" $ [s|%#X|] (3989525555 :: Word32) @?= "0XEDCB5433"
+    it "test-case #245" $ [s|%#o|] (0 :: Word32) @?= "0"
+    it "test-case #246" $ [s|%#x|] (0 :: Word32) @?= "0"
+    it "test-case #247" $ [s|%#X|] (0 :: Word32) @?= "0"
+    it "test-case #248" $ [s|%1s|] "Hallo heimur" @?= "Hallo heimur"
+    it "test-case #249" $ [s|%1d|] (1024 :: Int32) @?= "1024"
+    it "test-case #250" $ [s|%1d|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #251" $ [s|%1i|] (1024 :: Int32) @?= "1024"
+    it "test-case #252" $ [s|%1i|] (-1024 :: Int32) @?= "-1024"
+    it "test-case #253" $ [s|%1u|] (1024 :: Int32) @?= "1024"
+    it "test-case #254" $ [s|%1u|] (4294966272 :: Word32) @?= "4294966272"
+    it "test-case #255" $ [s|%1o|] (511 :: Int32) @?= "777"
+    it "test-case #256" $ [s|%1o|] (4294966785 :: Word32) @?= "37777777001"
+    it "test-case #257" $ [s|%1x|] (305441741 :: Int32) @?= "1234abcd"
+    it "test-case #258" $ [s|%1x|] (3989525555 :: Word32) @?= "edcb5433"
+    it "test-case #259" $ [s|%1X|] (305441741 :: Int32) @?= "1234ABCD"
+    it "test-case #260" $ [s|%1X|] (3989525555 :: Word32) @?= "EDCB5433"
+    it "test-case #261" $ [s|%1c|] 'x' @?= "x"
+    it "test-case #262" $ [s|%20s|] "Hallo" @?= "               Hallo"
+    it "test-case #263" $ [s|%20d|] (1024 :: Int32) @?= "                1024"
+    it "test-case #264" $ [s|%20d|] (-1024 :: Int32) @?= "               -1024"
+    it "test-case #265" $ [s|%20i|] (1024 :: Int32) @?= "                1024"
+    it "test-case #266" $ [s|%20i|] (-1024 :: Int32) @?= "               -1024"
+    it "test-case #267" $ [s|%20u|] (1024 :: Int32) @?= "                1024"
+    it "test-case #268" $ [s|%20u|] (4294966272 :: Word32) @?= "          4294966272"
+    it "test-case #269" $ [s|%20o|] (511 :: Int32) @?= "                 777"
+    it "test-case #270" $ [s|%20o|] (4294966785 :: Word32) @?= "         37777777001"
+    it "test-case #271" $ [s|%20x|] (305441741 :: Int32) @?= "            1234abcd"
+    it "test-case #272" $ [s|%20x|] (3989525555 :: Word32) @?= "            edcb5433"
+    it "test-case #273" $ [s|%20X|] (305441741 :: Int32) @?= "            1234ABCD"
+    it "test-case #274" $ [s|%20X|] (3989525555 :: Word32) @?= "            EDCB5433"
+    it "test-case #275" $ [s|%20c|] 'x' @?= "                   x"
+    it "test-case #276" $ [s|%-20s|] "Hallo" @?= "Hallo               "
+    it "test-case #277" $ [s|%-20d|] (1024 :: Int32) @?= "1024                "
+    it "test-case #278" $ [s|%-20d|] (-1024 :: Int32) @?= "-1024               "
+    it "test-case #279" $ [s|%-20i|] (1024 :: Int32) @?= "1024                "
+    it "test-case #280" $ [s|%-20i|] (-1024 :: Int32) @?= "-1024               "
+    it "test-case #281" $ [s|%-20u|] (1024 :: Int32) @?= "1024                "
+    it "test-case #282" $ [s|%-20u|] (4294966272 :: Word32) @?= "4294966272          "
+    it "test-case #283" $ [s|%-20o|] (511 :: Int32) @?= "777                 "
+    it "test-case #284" $ [s|%-20o|] (4294966785 :: Word32) @?= "37777777001         "
+    it "test-case #285" $ [s|%-20x|] (305441741 :: Int32) @?= "1234abcd            "
+    it "test-case #286" $ [s|%-20x|] (3989525555 :: Word32) @?= "edcb5433            "
+    it "test-case #287" $ [s|%-20X|] (305441741 :: Int32) @?= "1234ABCD            "
+    it "test-case #288" $ [s|%-20X|] (3989525555 :: Word32) @?= "EDCB5433            "
+    it "test-case #289" $ [s|%-20c|] 'x' @?= "x                   "
+    it "test-case #290" $ [s|%020d|] (1024 :: Int32) @?= "00000000000000001024"
+    it "test-case #291" $ [s|%020d|] (-1024 :: Int32) @?= "-0000000000000001024"
+    it "test-case #292" $ [s|%020i|] (1024 :: Int32) @?= "00000000000000001024"
+    it "test-case #293" $ [s|%020i|] (-1024 :: Int32) @?= "-0000000000000001024"
+    it "test-case #294" $ [s|%020u|] (1024 :: Int32) @?= "00000000000000001024"
+    it "test-case #295" $ [s|%020u|] (4294966272 :: Word32) @?= "00000000004294966272"
+    it "test-case #296" $ [s|%020o|] (511 :: Int32) @?= "00000000000000000777"
+    it "test-case #297" $ [s|%020o|] (4294966785 :: Word32) @?= "00000000037777777001"
+    it "test-case #298" $ [s|%020x|] (305441741 :: Int32) @?= "0000000000001234abcd"
+    it "test-case #299" $ [s|%020x|] (3989525555 :: Word32) @?= "000000000000edcb5433"
+    it "test-case #300" $ [s|%020X|] (305441741 :: Int32) @?= "0000000000001234ABCD"
+    it "test-case #301" $ [s|%020X|] (3989525555 :: Word32) @?= "000000000000EDCB5433"
+    it "test-case #302" $ [s|%#20o|] (511 :: Int32) @?= "                0777"
+    it "test-case #303" $ [s|%#20o|] (4294966785 :: Word32) @?= "        037777777001"
+    it "test-case #304" $ [s|%#20x|] (305441741 :: Int32) @?= "          0x1234abcd"
+    it "test-case #305" $ [s|%#20x|] (3989525555 :: Word32) @?= "          0xedcb5433"
+    it "test-case #306" $ [s|%#20X|] (305441741 :: Int32) @?= "          0X1234ABCD"
+    it "test-case #307" $ [s|%#20X|] (3989525555 :: Word32) @?= "          0XEDCB5433"
+    it "test-case #308" $ [s|%#020o|] (511 :: Int32) @?= "00000000000000000777"
+    it "test-case #309" $ [s|%#020o|] (4294966785 :: Word32) @?= "00000000037777777001"
+    it "test-case #310" $ [s|%#020x|] (305441741 :: Int32) @?= "0x00000000001234abcd"
+    it "test-case #311" $ [s|%#020x|] (3989525555 :: Word32) @?= "0x0000000000edcb5433"
+    it "test-case #312" $ [s|%#020X|] (305441741 :: Int32) @?= "0X00000000001234ABCD"
+    it "test-case #313" $ [s|%#020X|] (3989525555 :: Word32) @?= "0X0000000000EDCB5433"
+    it "test-case #314" $ [s|%0-20s|] "Hallo" @?= "Hallo               "
+    it "test-case #315" $ [s|%0-20d|] (1024 :: Int32) @?= "1024                "
+    it "test-case #316" $ [s|%0-20d|] (-1024 :: Int32) @?= "-1024               "
+    it "test-case #317" $ [s|%0-20i|] (1024 :: Int32) @?= "1024                "
+    it "test-case #318" $ [s|%0-20i|] (-1024 :: Int32) @?= "-1024               "
+    it "test-case #319" $ [s|%0-20u|] (1024 :: Int32) @?= "1024                "
+    it "test-case #320" $ [s|%0-20u|] (4294966272 :: Word32) @?= "4294966272          "
+    it "test-case #321" $ [s|%-020o|] (511 :: Int32) @?= "777                 "
+    it "test-case #322" $ [s|%-020o|] (4294966785 :: Word32) @?= "37777777001         "
+    it "test-case #323" $ [s|%-020x|] (305441741 :: Int32) @?= "1234abcd            "
+    it "test-case #324" $ [s|%-020x|] (3989525555 :: Word32) @?= "edcb5433            "
+    it "test-case #325" $ [s|%-020X|] (305441741 :: Int32) @?= "1234ABCD            "
+    it "test-case #326" $ [s|%-020X|] (3989525555 :: Word32) @?= "EDCB5433            "
+    it "test-case #327" $ [s|%-020c|] 'x' @?= "x                   "
+    it "test-case #328" $ [s|%*s|] (20 :: Int32) "Hallo" @?= "               Hallo"
+    it "test-case #329" $
+        [s|%*d|] (20 :: Int32) (1024 :: Int32) @?= "                1024"
+    it "test-case #330" $
+        [s|%*d|] (20 :: Int32) (-1024 :: Int32) @?= "               -1024"
+    it "test-case #331" $
+        [s|%*i|] (20 :: Int32) (1024 :: Int32) @?= "                1024"
+    it "test-case #332" $
+        [s|%*i|] (20 :: Int32) (-1024 :: Int32) @?= "               -1024"
+    it "test-case #333" $
+        [s|%*u|] (20 :: Int32) (1024 :: Int32) @?= "                1024"
+    it "test-case #334" $
+        [s|%*u|] (20 :: Int32) (4294966272 :: Word32) @?= "          4294966272"
+    it "test-case #335" $ [s|%*o|] (20 :: Int32) (511 :: Int32) @?= "                 777"
+    it "test-case #336" $
+        [s|%*o|] (20 :: Int32) (4294966785 :: Word32) @?= "         37777777001"
+    it "test-case #337" $
+        [s|%*x|] (20 :: Int32) (305441741 :: Int32) @?= "            1234abcd"
+    it "test-case #338" $
+        [s|%*x|] (20 :: Int32) (3989525555 :: Word32) @?= "            edcb5433"
+    it "test-case #339" $
+        [s|%*X|] (20 :: Int32) (305441741 :: Int32) @?= "            1234ABCD"
+    it "test-case #340" $
+        [s|%*X|] (20 :: Int32) (3989525555 :: Word32) @?= "            EDCB5433"
+    it "test-case #341" $ [s|%*c|] (20 :: Int32) 'x' @?= "                   x"
+    it "test-case #342" $ [s|%.20s|] "Hallo heimur" @?= "Hallo heimur"
+    it "test-case #343" $ [s|%.20d|] (1024 :: Int32) @?= "00000000000000001024"
+    it "test-case #344" $ [s|%.20d|] (-1024 :: Int32) @?= "-00000000000000001024"
+    it "test-case #345" $ [s|%.20i|] (1024 :: Int32) @?= "00000000000000001024"
+    it "test-case #346" $ [s|%.20i|] (-1024 :: Int32) @?= "-00000000000000001024"
+    it "test-case #347" $ [s|%.20u|] (1024 :: Int32) @?= "00000000000000001024"
+    it "test-case #348" $ [s|%.20u|] (4294966272 :: Word32) @?= "00000000004294966272"
+    it "test-case #349" $ [s|%.20o|] (511 :: Int32) @?= "00000000000000000777"
+    it "test-case #350" $ [s|%.20o|] (4294966785 :: Word32) @?= "00000000037777777001"
+    it "test-case #351" $ [s|%.20x|] (305441741 :: Int32) @?= "0000000000001234abcd"
+    it "test-case #352" $ [s|%.20x|] (3989525555 :: Word32) @?= "000000000000edcb5433"
+    it "test-case #353" $ [s|%.20X|] (305441741 :: Int32) @?= "0000000000001234ABCD"
+    it "test-case #354" $ [s|%.20X|] (3989525555 :: Word32) @?= "000000000000EDCB5433"
+    it "test-case #355" $ [s|%20.5s|] "Hallo heimur" @?= "               Hallo"
+    it "test-case #356" $ [s|%20.5d|] (1024 :: Int32) @?= "               01024"
+    it "test-case #357" $ [s|%20.5d|] (-1024 :: Int32) @?= "              -01024"
+    it "test-case #358" $ [s|%20.5i|] (1024 :: Int32) @?= "               01024"
+    it "test-case #359" $ [s|%20.5i|] (-1024 :: Int32) @?= "              -01024"
+    it "test-case #360" $ [s|%20.5u|] (1024 :: Int32) @?= "               01024"
+    it "test-case #361" $ [s|%20.5u|] (4294966272 :: Word32) @?= "          4294966272"
+    it "test-case #362" $ [s|%20.5o|] (511 :: Int32) @?= "               00777"
+    it "test-case #363" $ [s|%20.5o|] (4294966785 :: Word32) @?= "         37777777001"
+    it "test-case #364" $ [s|%20.5x|] (305441741 :: Int32) @?= "            1234abcd"
+    it "test-case #365" $ [s|%20.10x|] (3989525555 :: Word32) @?= "          00edcb5433"
+    it "test-case #366" $ [s|%20.5X|] (305441741 :: Int32) @?= "            1234ABCD"
+    it "test-case #367" $ [s|%20.10X|] (3989525555 :: Word32) @?= "          00EDCB5433"
+    it "test-case #369" $ [s|%020.5d|] (1024 :: Int32) @?= "               01024"
+    it "test-case #370" $ [s|%020.5d|] (-1024 :: Int32) @?= "              -01024"
+    it "test-case #371" $ [s|%020.5i|] (1024 :: Int32) @?= "               01024"
+    it "test-case #372" $ [s|%020.5i|] (-1024 :: Int32) @?= "              -01024"
+    it "test-case #373" $ [s|%020.5u|] (1024 :: Int32) @?= "               01024"
+    it "test-case #374" $ [s|%020.5u|] (4294966272 :: Word32) @?= "          4294966272"
+    it "test-case #375" $ [s|%020.5o|] (511 :: Int32) @?= "               00777"
+    it "test-case #376" $ [s|%020.5o|] (4294966785 :: Word32) @?= "         37777777001"
+    it "test-case #377" $ [s|%020.5x|] (305441741 :: Int32) @?= "            1234abcd"
+    it "test-case #378" $ [s|%020.10x|] (3989525555 :: Word32) @?= "          00edcb5433"
+    it "test-case #379" $ [s|%020.5X|] (305441741 :: Int32) @?= "            1234ABCD"
+    it "test-case #380" $ [s|%020.10X|] (3989525555 :: Word32) @?= "          00EDCB5433"
+    it "test-case #381" $ [s|%.0s|] "Hallo heimur" @?= ""
+    it "test-case #382" $ [s|%20.0s|] "Hallo heimur" @?= "                    "
+    it "test-case #383" $ [s|%.s|] "Hallo heimur" @?= ""
+    it "test-case #384" $ [s|%20.s|] "Hallo heimur" @?= "                    "
+    it "test-case #385" $ [s|%20.0d|] (1024 :: Int32) @?= "                1024"
+    it "test-case #386" $ [s|%20.d|] (-1024 :: Int32) @?= "               -1024"
+    it "test-case #387" $ [s|%20.d|] (0 :: Int32) @?= "                    "
+    it "test-case #388" $ [s|%20.0i|] (1024 :: Int32) @?= "                1024"
+    it "test-case #389" $ [s|%20.i|] (-1024 :: Int32) @?= "               -1024"
+    it "test-case #390" $ [s|%20.i|] (0 :: Int32) @?= "                    "
+    it "test-case #391" $ [s|%20.u|] (1024 :: Int32) @?= "                1024"
+    it "test-case #392" $ [s|%20.0u|] (4294966272 :: Word32) @?= "          4294966272"
+    it "test-case #393" $ [s|%20.u|] (0 :: Word32) @?= "                    "
+    it "test-case #394" $ [s|%20.o|] (511 :: Int32) @?= "                 777"
+    it "test-case #395" $ [s|%20.0o|] (4294966785 :: Word32) @?= "         37777777001"
+    it "test-case #396" $ [s|%20.o|] (0 :: Word32) @?= "                    "
+    it "test-case #397" $ [s|%20.x|] (305441741 :: Int32) @?= "            1234abcd"
+    it "test-case #398" $ [s|%20.0x|] (3989525555 :: Word32) @?= "            edcb5433"
+    it "test-case #399" $ [s|%20.x|] (0 :: Word32) @?= "                    "
+    it "test-case #400" $ [s|%20.X|] (305441741 :: Int32) @?= "            1234ABCD"
+    it "test-case #401" $ [s|%20.0X|] (3989525555 :: Word32) @?= "            EDCB5433"
+    it "test-case #402" $ [s|%20.X|] (0 :: Word32) @?= "                    "
+    it "test-case #403" $
+        [s|% -0+*.*s|] (20 :: Int32) (5 :: Int32) "Hallo heimur" @?=
+        "Hallo               "
+    it "test-case #404" $
+        [s|% -0+*.*d|] (20 :: Int32) (5 :: Int32) (1024 :: Int32) @?=
+        "+01024              "
+    it "test-case #405" $
+        [s|% -0+*.*d|] (20 :: Int32) (5 :: Int32) (-1024 :: Int32) @?=
+        "-01024              "
+    it "test-case #406" $
+        [s|% -0+*.*i|] (20 :: Int32) (5 :: Int32) (1024 :: Int32) @?=
+        "+01024              "
+    it "test-case #407" $
+        [s|% 0-+*.*i|] (20 :: Int32) (5 :: Int32) (-1024 :: Int32) @?=
+        "-01024              "
+    it "test-case #408" $
+        [s|% 0-+*.*u|] (20 :: Int32) (5 :: Int32) (1024 :: Int32) @?=
+        "01024               "
+    it "test-case #409" $
+        [s|% 0-+*.*u|] (20 :: Int32) (5 :: Int32) (4294966272 :: Word32) @?=
+        "4294966272          "
+    it "test-case #410" $
+        [s|%+ -0*.*o|] (20 :: Int32) (5 :: Int32) (511 :: Int32) @?=
+        "00777               "
+    it "test-case #411" $
+        [s|%+ -0*.*o|] (20 :: Int32) (5 :: Int32) (4294966785 :: Word32) @?=
+        "37777777001         "
+    it "test-case #412" $
+        [s|%+ -0*.*x|] (20 :: Int32) (5 :: Int32) (305441741 :: Int32) @?=
+        "1234abcd            "
+    it "test-case #413" $
+        [s|%+ -0*.*x|] (20 :: Int32) (10 :: Int32) (3989525555 :: Word32) @?=
+        "00edcb5433          "
+    it "test-case #414" $
+        [s|% -+0*.*X|] (20 :: Int32) (5 :: Int32) (305441741 :: Int32) @?=
+        "1234ABCD            "
+    it "test-case #415" $
+        [s|% -+0*.*X|] (20 :: Int32) (10 :: Int32) (3989525555 :: Word32) @?=
+        "00EDCB5433          "
+    it "test-case #416" $ [s|%*sx|] (-3 :: Int32) "hi" @?= "hi x"
diff --git a/tests/printf/format.hs b/tests/printf/format.hs
new file mode 100644
--- /dev/null
+++ b/tests/printf/format.hs
@@ -0,0 +1,47 @@
+{-# OPTIONS_GHC -Wwarn #-}
+{-# LANGUAGE QuasiQuotes #-}
+
+module Main where
+
+import           Foreign.Ptr
+import           GeneratedSpec
+import           Language.Haskell.Printf
+import           Test.HUnit
+import           Test.Hspec
+import qualified Data.Text                     as S
+import qualified Data.Text.Lazy                as L
+
+main :: IO ()
+main = hspec $ describe "th-printf" $ do
+  GeneratedSpec.spec
+  it "text" $ do
+    -- sanity checking that text can be rendered
+    -- all the actual string formatting is in GeneratedSpec
+    [s|Hello, %Q!|] (S.pack "world") @?= "Hello, world!"
+    [s|Hello, %q!|] (L.pack "world") @?= "Hello, world!"
+  it "hexadecimal float" $ do
+    [s|%a|] 0.857421875 @?= "0x1.b7p-1"
+    [s|%A|] 3.1415926 @?= "0X1.921FB4D12D84AP+1"
+    [s|%.3a|] 1.999999999 @?= "0x1.000p+1"
+    [s|%.0a|] 1.999999999 @?= "0x1p+1"
+    [s|%#.0a|] 1.999999999 @?= "0x1.p+1"
+    [s|%.3a|] 0.7576 @?= "0x1.83ep-1"
+    [s|%015.3a|] 0.7576 @?= "0x000001.83ep-1"
+    [s|% 15.3a|] 0.7576 @?= "     0x1.83ep-1"
+  it "Show instances" $ do
+    [s|%?|] () @?= "()"
+    [s|%10?|] () @?= "        ()"
+  it "pointer" $ do
+    [s|%p|] nullPtr @?= "0x0"
+    [s|%15p|] fakePtr @?= "     0xdeadbeef"
+    -- sign flag does nothing
+    [s|%+p|] fakePtr @?= "0xdeadbeef"
+    -- prefix flag does nothing
+    [s|%#p|] fakePtr @?= "0xdeadbeef"
+    -- zero flag does nothing
+    [s|%015p|] fakePtr @?= "     0xdeadbeef"
+    -- left-pad flag does nothing
+    [s|%-15p|] fakePtr @?= "     0xdeadbeef"
+
+fakePtr :: Ptr ()
+fakePtr = nullPtr `plusPtr` 0xdeadbeef
diff --git a/th-printf.cabal b/th-printf.cabal
--- a/th-printf.cabal
+++ b/th-printf.cabal
@@ -1,15 +1,15 @@
-cabal-version: >= 1.10
+cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.29.6.
+-- This file has been generated from package.yaml by hpack version 0.32.0.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 02f18fcc2963d061d9c5bff9a6cd352518b69cf49131279a887e54cc33c0ea3e
+-- hash: 62c8df7d3174235654f8edabd47054d66b3551339b9b409b40da4d405dd12733
 
 name:           th-printf
-version:        0.6.0
+version:        0.7
 synopsis:       Quasiquoters for printf
-description:    Quasiquoters for printf: string, bytestring, text.
+description:    Quasiquoters for string and text printf
 category:       Text
 homepage:       https://github.com/pikajude/th-printf#readme
 bug-reports:    https://github.com/pikajude/th-printf/issues
@@ -17,8 +17,11 @@
 maintainer:     me@jude.xyz
 license:        MIT
 license-file:   LICENSE
-tested-with:    GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.3
+tested-with:    GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.1
 build-type:     Simple
+extra-source-files:
+    CHANGELOG.md
+    include/compat-defs.h
 
 source-repository head
   type: git
@@ -34,6 +37,7 @@
       Language.Haskell.Printf
       Language.Haskell.Printf.Lib
   other-modules:
+      Buildable
       Language.Haskell.Printf.Geometry
       Language.Haskell.Printf.Printers
       Language.Haskell.PrintfArg
@@ -45,18 +49,26 @@
   hs-source-dirs:
       src
       parser
+  default-extensions: CPP
   ghc-options: -Wall
+  cpp-options: -include include/compat-defs.h
   build-depends:
       base ==4.*
     , charset
     , containers
+    , dlist
+    , integer-logarithms
     , microlens-platform
     , mtl
     , parsec
     , semigroups
     , template-haskell
+    , text
     , th-lift
     , transformers
+  if impl(ghcjs)
+    build-depends:
+        primitive ==0.6.3.0
   if flag(werror)
     ghc-options: -Werror
   default-language: Haskell2010
@@ -68,15 +80,22 @@
       GeneratedSpec
       Paths_th_printf
   hs-source-dirs:
-      tests
-  ghc-options: -Wall
+      tests/printf
+  ghc-options: -Wall -fno-warn-type-defaults
   build-depends:
       HUnit
     , QuickCheck
     , base ==4.*
     , hspec
     , template-haskell
+    , text
     , th-printf
+  if impl(ghcjs)
+    build-depends:
+        primitive ==0.6.3.0
   if flag(werror)
     ghc-options: -Werror
+  if impl(ghcjs)
+    build-depends:
+        hspec-core ==2.4.8
   default-language: Haskell2010
