diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,20 @@
-The MIT License (MIT)
-
-Copyright (c) 2013 Joel Taylor
+Copyright (c) 2018 Jude Taylor
 
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
 
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/benchmark/benchmark.hs b/benchmark/benchmark.hs
deleted file mode 100644
--- a/benchmark/benchmark.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# Language QuasiQuotes #-}
-
-module Main
-    ( main
-    ) where
-
-import Criterion
-import Criterion.Main
-import Text.Printf.TH
-
-main :: IO ()
-main =
-    defaultMain
-        [ env (pure ([s|test %50s|], [st|test %50s|])) $ \ ~(fs, ft) ->
-              bgroup "string" [bench "s" $ nf fs "foobar", bench "st" $ nf ft "foobar"]
-        , bgroup "int" [bench "s" $ nf [s|%010d|] 20, bench "st" $ nf [st|%010d|] 20]
-        ]
diff --git a/parser/Parser.hs b/parser/Parser.hs
new file mode 100644
--- /dev/null
+++ b/parser/Parser.hs
@@ -0,0 +1,144 @@
+{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
+
+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)
+
+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"
+
+normalizeAndWarn :: Atom -> (Atom, [Warning])
+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 ()
+
+flagSet :: CharSet
+flagSet = fromList "-+ #0"
+
+specSet :: CharSet
+specSet = fromList "diuoxXfFeEaAgGpcs?"
+
+lengthSpecifiers :: [(String, LengthSpecifier)]
+lengthSpecifiers =
+    [ ("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 [] = []
+
+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)
diff --git a/parser/Parser/Types.hs b/parser/Parser/Types.hs
new file mode 100644
--- /dev/null
+++ b/parser/Parser/Types.hs
@@ -0,0 +1,112 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE NamedFieldPuns #-}
+
+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)
+
+data Atom
+    = Arg FormatArg
+    | Str String
+    deriving (Show)
+
+data LengthSpecifier
+    = DoubleH
+    | H
+    | DoubleL
+    | BigL
+    | L
+    | J
+    | Z
+    | T
+    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"
+
+data Flag
+    = FlagLJust
+    | FlagSigned
+    | FlagSpaced
+    | FlagPrefixed
+    | FlagZeroPadded
+    deriving (Show, Eq, Ord)
+
+adjustmentFlags :: Set Flag
+adjustmentFlags = S.fromList [FlagLJust, FlagZeroPadded]
+
+data Adjustment
+    = LeftJustified
+    | ZeroPadded
+    deriving (Show, Eq)
+
+data MaySpecify
+    = Given Integer
+    | Need
+    deriving (Show)
+
+data FormatArg = FormatArg
+    { flags :: FlagSet
+    , width :: Maybe MaySpecify
+    , precision :: Maybe MaySpecify
+    , spec :: Char
+    , lengthSpec :: Maybe LengthSpecifier
+    } deriving (Show)
+
+type FormatStr = [Atom]
+
+data FlagSet = FlagSet
+    { adjustment :: Maybe Adjustment
+    , signed :: Bool
+    , spaced :: Bool
+    , prefixed :: Bool
+    } deriving (Show)
+
+emptyFlagSet :: FlagSet
+emptyFlagSet = FlagSet Nothing False False False
+
+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
+            }
+
+makeLensesFor
+    [ ("adjustment", "adjustment_")
+    , ("signed", "signed_")
+    , ("spaced", "spaced_")
+    , ("prefixed", "prefixed_")
+    ]
+    ''FlagSet
+
+makeLensesFor
+    [ ("flags", "flags_")
+    , ("width", "width_")
+    , ("precision", "precision_")
+    , ("spec", "spec_")
+    , ("lengthSpec", "lengthSpec_")
+    ]
+    ''FormatArg
+
+deriveLiftMany [''Adjustment, ''FlagSet, ''LengthSpecifier]
diff --git a/src/Language/Haskell/Printf.hs b/src/Language/Haskell/Printf.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Haskell/Printf.hs
@@ -0,0 +1,100 @@
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE QuasiQuotes #-}
+
+module Language.Haskell.Printf
+    ( s
+    , 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)
+
+-- | @
+-- ['s'|Hello, %s! (%d people greeted)|] :: ... -> 'String'
+-- @
+--
+-- This formatter follows the guidelines listed
+-- <http://www.cplusplus.com/reference/cstdio/printf/ here>, except for
+-- @%n@ (store number of printed characters) for obvious
+-- reasons.
+--
+-- @
+-- %c     :: 'Char'
+-- %s     :: 'String'
+--
+-- -- datatypes with Show instances
+-- %?     :: 'Show' a => a
+--
+-- -- signed integer types
+-- %d, %i :: 'Integral' i => i
+--
+-- -- unsigned integer types
+-- %u     :: ('Bounded' i, 'Integral' i) => i
+-- %o     :: ('Bounded' i, 'Integral' i) => i
+-- %x, %X :: ('Bounded' i, 'Integral' i) => i
+--
+-- -- floats
+-- %a, %A :: 'RealFloat' f => f
+-- %e, %E :: 'RealFloat' f => f
+-- %f, %F :: 'RealFloat' f => f
+-- %g, %G :: 'RealFloat' f => f
+--
+-- %p     :: 'Foreign.Ptr.Ptr' a
+-- @
+--
+-- 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
+        }
+
+-- | 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))|]
+        }
+
+-- | Like 'p', but takes as its first argument the 'System.IO.Handle' to print to.
+--
+-- @
+-- [hp|Hello, %s! (%d people greeted)|] :: 'MonadIO' m => 'System.IO.Handle' -> ... -> m ()
+-- @
+hp :: QuasiQuoter
+hp =
+    quoter
+        { quoteExp =
+              \s' -> do
+                  (lhss, rhs) <- toSplices s'
+                  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"
+        }
diff --git a/src/Language/Haskell/Printf/Geometry.hs b/src/Language/Haskell/Printf/Geometry.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Haskell/Printf/Geometry.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+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
+
+data Value = Value
+    { valArg :: PrintfArg String
+    , valPrefix :: Maybe String
+    , valSign :: Maybe String
+    } 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
+
+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)
+
+prefix :: (Num n, Eq n) => String -> PrintfArg n -> Maybe String
+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 f b c a = Value (f <$> a) (b a) (c a)
+
+formatOne :: Value -> String
+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
diff --git a/src/Language/Haskell/Printf/Lib.hs b/src/Language/Haskell/Printf/Lib.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Haskell/Printf/Lib.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Language.Haskell.Printf.Lib
+    ( toSplices
+    ) 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)
+
+-- | 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.
+-- It will emit warnings (or throw an error, as appropriate) when given an invalid format
+-- string.
+--
+-- 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')
+
+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
diff --git a/src/Language/Haskell/Printf/Printers.hs b/src/Language/Haskell/Printf/Printers.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Haskell/Printf/Printers.hs
@@ -0,0 +1,149 @@
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# 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
+
+type Printer n = PrintfArg n -> Value
+
+printfString :: Printer String
+printfString spec =
+    Value
+        { valArg =
+              case prec spec of
+                  Nothing -> spec
+                  Just c -> take c <$> spec
+        , valPrefix = Nothing
+        , valSign = Nothing
+        }
+
+printfShow :: Show a => Printer a
+printfShow spec = printfString (fromString . show <$> spec)
+
+printfChar :: Printer Char
+printfChar spec = Value {valArg = pure <$> 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}
+                  , lengthSpec = Nothing
+                  , fieldSpec = 'p'
+                  , value = showIntAtBase 16 intToDigit (toInt $ value spec)
+                  }
+        , valPrefix = Just "0x"
+        , valSign = Nothing
+        }
+  where
+    toInt x = x `minusPtr` nullPtr
+
+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
+
+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
+
+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
+
+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
diff --git a/src/Language/Haskell/PrintfArg.hs b/src/Language/Haskell/PrintfArg.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Haskell/PrintfArg.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE DeriveFunctor #-}
+
+module Language.Haskell.PrintfArg where
+
+import qualified Parser.Types as P
+
+data PrintfArg v = PrintfArg
+    { flagSet :: P.FlagSet
+    , width :: Maybe Int
+    , prec :: Maybe Int
+    , lengthSpec :: Maybe P.LengthSpecifier
+    , fieldSpec :: Char
+    , value :: v
+    } deriving (Show, Functor)
+
+adjustment :: PrintfArg v -> Maybe P.Adjustment
+adjustment = P.adjustment . flagSet
+
+signed, spaced, prefixed :: PrintfArg v -> Bool
+signed = P.signed . flagSet
+
+spaced = P.spaced . flagSet
+
+prefixed = P.prefixed . flagSet
diff --git a/src/NumUtils.hs b/src/NumUtils.hs
new file mode 100644
--- /dev/null
+++ b/src/NumUtils.hs
@@ -0,0 +1,153 @@
+{-# 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
+
+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
+
+formatRealFloatAlt :: RealFloat a => FFFormat -> Maybe Int -> Bool -> Bool -> a -> String
+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]
+
+toRoundedDigits :: Maybe Int -> ([Int], Int) -> Bool -> ([Int], Int)
+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
+
+fromDigits :: Bool -> [Int] -> String
+fromDigits upper =
+    foldr
+        ((:) .
+         id .
+         (if upper
+              then toUpper
+              else id) .
+         intToDigit)
+        []
+
+formatHexFloat :: RealFloat a => Maybe Int -> Bool -> Bool -> a -> String
+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 [] = []
diff --git a/src/NumericUtils.hs b/src/NumericUtils.hs
deleted file mode 100644
--- a/src/NumericUtils.hs
+++ /dev/null
@@ -1,162 +0,0 @@
-{-# Language MagicHash #-}
-
-module NumericUtils
-    ( module NumericUtils
-    , intToDigit
-    , FFFormat(..)
-    ) where
-
-import Data.Char
-import Data.Foldable
-import Data.Ord
-import GHC.Base
-import GHC.Float (FFFormat(..), roundTo)
-import Numeric (floatToDigits)
-import Text.Printf.TH.Printer
-
-intToDigitUpper :: Int -> Char
-intToDigitUpper (I# i)
-    | isTrue# (i >=# 0#) && isTrue# (i <=# 9#) = unsafeChr (ord '0' + I# i)
-    | isTrue# (i >=# 10#) && isTrue# (i <=# 15#) = unsafeChr (ord 'A' + I# i - 10)
-    | otherwise = error ("Char.intToDigit: not a digit " ++ show (I# i))
-
-showIntAtBase :: (Integral i, Monoid b, Printer b) => i -> (Int -> Char) -> i -> b
-showIntAtBase base toB n0 = showIt (quotRem n0 base) mempty
-  where
-    showIt (n, d) r =
-        case n of
-            0 -> r'
-            _ -> showIt (quotRem n base) r'
-      where
-        r' = cons (toB $ fromIntegral d) r
-
--- based on GHC.Float.formatRealFloatAlt, except this adds an uppercasing
--- flag and "fixes" (imo) the (Just 0) precision case
-formatRealFloatAlt :: RealFloat a => FFFormat -> Maybe Int -> Bool -> Bool -> a -> String
-formatRealFloatAlt fmt decs alt upper x
-    | isNaN x = "NaN"
-    | isInfinite x =
-        if x < 0
-            then "-Infinity"
-            else "Infinity"
-    | x < 0 || isNegativeZero x = '-' : doFmt fmt (floatToDigits base (-x))
-    | otherwise = doFmt fmt (floatToDigits base x)
-  where
-    eChar
-        | upper = 'E'
-        | otherwise = 'e'
-    base = 10
-    doFmt format (is, e) =
-        let ds = map intToDigit is
-         in case format of
-                FFGeneric ->
-                    minimumBy
-                        (comparing length)
-                        [doFmt FFExponent (is, e), doFmt FFFixed (is, e)]
-                FFExponent ->
-                    case decs of
-                        Nothing ->
-                            let show_e' = show (e - 1)
-                             in case ds of
-                                    "0" -> "0.0" ++ (eChar : "0")
-                                    [d] -> d : ".0" ++ [eChar] ++ show_e'
-                                    (d:ds') -> d : '.' : ds' ++ [eChar] ++ show_e'
-                                    [] -> error "formatRealFloat/doFmt/FFExponent: []"
-                        Just dec ->
-                            let dec' = max dec 0
-                             in case is of
-                                    [0] ->
-                                        (if dec' == 0
-                                             then "0"
-                                             else "0.") ++
-                                        take dec' (repeat '0') ++ (eChar : "0")
-                                    _ ->
-                                        let (ei, is') = roundTo base (dec' + 1) is
-                                            (d:ds') =
-                                                map
-                                                    intToDigit
-                                                    (if ei > 0
-                                                         then init is'
-                                                         else is')
-                                         in (if null ds' && not alt
-                                                 then [d]
-                                                 else d : ".") ++
-                                            ds' ++ eChar : show (e - 1 + ei)
-                FFFixed ->
-                    let mk0 ls =
-                            case ls of
-                                "" -> "0"
-                                _ -> ls
-                     in case decs of
-                            Nothing
-                                | e <= 0 -> "0." ++ replicate (-e) '0' ++ ds
-                                | otherwise ->
-                                    let f 0 s rs = mk0 (reverse s) ++ '.' : mk0 rs
-                                        f n s "" = f (n - 1) ('0' : s) ""
-                                        f n s (r:rs) = f (n - 1) (r : s) rs
-                                     in f e "" ds
-                            Just dec ->
-                                let dec' = max dec 0
-                                 in if e >= 0
-                                        then let (ei, is') = roundTo base (dec' + e) is
-                                                 (ls, rs) =
-                                                     splitAt (e + ei) (map intToDigit is')
-                                              in mk0 ls ++
-                                                 (if null rs && not alt
-                                                      then ""
-                                                      else '.' : rs)
-                                        else let (ei, is') =
-                                                     roundTo
-                                                         base
-                                                         dec'
-                                                         (replicate (-e) 0 ++ is)
-                                                 d:ds' =
-                                                     map
-                                                         intToDigit
-                                                         (if ei > 0
-                                                              then is'
-                                                              else 0 : is')
-                                              in d :
-                                                 (if null ds' && not alt
-                                                      then ""
-                                                      else '.' : ds')
-
--- we don't add 0x here because it's handled by the formatter
-formatFloatHex :: RealFloat a => Maybe Int -> Bool -> Bool -> a -> String
-formatFloatHex decs alt upper x = doFmt (floatToDigits 2 x)
-  where
-    pChar
-        | upper = 'P'
-        | otherwise = 'p'
-    round' =
-        case decs of
-            Just d ->
-                \x ->
-                    let (a, b) = roundTo 16 d x
-                     in ( intToDigit (a + 1)
-                        , if a > 0
-                              then tail b
-                              else b)
-            Nothing -> \x -> ('1', x)
-    doFmt ([0], 0) = "0" ++ [pChar] ++ "+0"
-    doFmt ((_:bits), exp) =
-        first :
-        (if null digs && not alt
-             then ""
-             else ".") ++
-        map
-            (if upper
-                 then intToDigitUpper
-                 else intToDigit)
-            digs ++
-        [pChar] ++
-        (if exp >= 1
-             then "+"
-             else "") ++
-        show (exp - 1)
-      where
-        (first, digs) = round' $ go bits
-        go (a:b:c:d:xs) = foldl (\a b -> 2 * a + b) 0 [a, b, c, d] : go xs
-        go [] = []
-        go ys = go (take 4 $ ys ++ repeat 0)
-    doFmt _ = error "nonsense"
diff --git a/src/StrUtils.hs b/src/StrUtils.hs
new file mode 100644
--- /dev/null
+++ b/src/StrUtils.hs
@@ -0,0 +1,15 @@
+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
+
+justifyRight :: Int -> Char -> String -> String
+justifyRight n c s
+    | diff <= 0 = s
+    | otherwise = replicate diff c ++ s
+  where
+    diff = n - length s
diff --git a/src/Text/Printf/TH.hs b/src/Text/Printf/TH.hs
deleted file mode 100644
--- a/src/Text/Printf/TH.hs
+++ /dev/null
@@ -1,201 +0,0 @@
-{-# Language CPP #-}
-{-# Language DeriveDataTypeable #-}
-{-# Language FlexibleInstances #-}
-{-# Language QuasiQuotes #-}
-{-# Language RecordWildCards #-}
-{-# Language ScopedTypeVariables #-}
-{-# Language TemplateHaskell #-}
-{-# Language TupleSections #-}
-
-module Text.Printf.TH
-    ( -- * Formatting strings
-      s
-    , st
-    , sb
-      -- * Printing strings
-    , hp
-    , p
-    ) where
-
-import Control.Exception (Exception, throw)
-import Control.Monad.IO.Class
-import Data.ByteString.UTF8 (fromString)
-import Data.Maybe
-import Data.Proxy (Proxy(..))
-import Data.Text (pack)
-import Language.Haskell.TH
-import Language.Haskell.TH.Quote
-import Language.Haskell.TH.Syntax
-import System.IO
-import Text.PrettyPrint.ANSI.Leijen as Pretty hiding ((<$>), (<>), empty, line)
-import Text.Trifecta
-
-import Text.Printf.TH.Parser
-import Text.Printf.TH.Printer
-import Text.Printf.TH.Printer.String ()
-import Text.Printf.TH.Types
-
-data ParseError =
-    ParseError
-    deriving (Show)
-
-instance Exception ParseError
-
--- * Formatting strings
--- | @
--- ['s'|Hello, %s! (%d people greeted)|] :: ... -> String
--- @
---
--- This formatter follows the guidelines listed
--- <http://www.cplusplus.com/reference/cstdio/printf/ here>, except for
--- @%n@ (store number of printed characters) for obvious
--- reasons.
---
--- @
--- %c     :: 'Char'
--- %s     :: 'String'
---
--- %?     :: 'Show' a => a
---
--- %u     :: 'Numeric.Natural.Natural'
---
--- %d, %i :: 'Integral' i => i
--- %o     :: 'Integral' i => i
--- %x, %X :: 'Integral' i => i
---
--- %a, %A :: 'RealFloat' f => f
--- %e, %E :: 'RealFloat' f => f
--- %f, %F :: 'RealFloat' f => f
--- %g, %G :: 'RealFloat' f => f
---
--- %p     :: 'Foreign.Ptr.Ptr' a
--- @
-s :: QuasiQuoter
-s = quoter 'id
-
--- | @
--- ['st'|Hello, %s! (%d people greeted)|] :: ... -> 'Data.Text.Text'
--- @
-st :: QuasiQuoter
-st = quoter 'pack
-
--- | @
--- ['sb'|Hello, %s! (%d people greeted)|] :: ... -> 'Data.ByteString.ByteString'
--- @
---
--- The resulting string is UTF8-encoded.
-sb :: QuasiQuoter
-sb = quoter 'fromString
-
--- | @
--- ['hp'|Hello, %s! (%d people greeted)|] :: 'MonadIO' m => 'Handle' -> ... -> m ()
--- @
---
--- Prints the produced string to the provided 'Handle'. Like C printf, newline is not
--- appended.
-hp :: QuasiQuoter
-hp =
-    QuasiQuoter
-        { quoteExp =
-              \s -> do
-                  LamE pats body <- parse 'id s
-                  arg <- newName "arg"
-                  ps <- [|liftIO . hPutStr $(varE arg)|]
-                  pure $ LamE ((VarP arg) : pats) $ AppE ps body
-        , quotePat = error "printf cannot be used in a pattern context"
-        , quoteType = error "printf cannot be used in a type context"
-        , quoteDec = error "printf cannot be used in a decl context"
-        }
-
--- | @
--- ['p'|Hello, %s! (%d people greeted)|] :: 'MonadIO' m => ... -> m ()
--- @
---
--- @
--- [p|...|] arg1 arg2...
--- @
---
--- is equivalent to
---
--- @
--- [hp|...|] 'System.IO.stdout' arg1 arg2...
--- @
-p :: QuasiQuoter
-p =
-    QuasiQuoter
-        { quoteExp =
-              \s -> do
-                  LamE pats body <- parse 'id s
-                  ps <- [|liftIO . putStr|]
-                  pure $ LamE pats $ AppE ps body
-        , quotePat = error "printf cannot be used in a pattern context"
-        , quoteType = error "printf cannot be used in a type context"
-        , quoteDec = error "printf cannot be used in a decl context"
-        }
-
-quoter f =
-    QuasiQuoter
-        { quoteExp = parse f
-        , quotePat = error "printf cannot be used in a pattern context"
-        , quoteType = error "printf cannot be used in a type context"
-        , quoteDec = error "printf cannot be used in a decl context"
-        }
-
-parse f s =
-    case parseString parseFmtStr mempty s of
-        Success fmtStr -> build f ''String fmtStr
-        Failure xs -> do
-            runIO $ displayIO stderr $ renderPretty 0.8 80 $ _errDoc xs
-            throw ParseError
-
-build f outputType fmtStr = do
-    pairs <- mapM mkExpr fmtStr
-    let (args, exprs) = unzip pairs
-    lamE (map varP $ concat args) $
-        appE (varE f) $
-        appsE [[|finalize|], [|Proxy :: Proxy $(conT outputType)|], listE exprs]
-
-mkExpr (Str s) = pure ([] :: [Name], [|valOf (literal $(stringE s))|])
-mkExpr (Arg (FormatArg flags width precision spec)) = do
-    (warg, wexp) <- extractArgs width
-    (parg, pexp) <- extractArgs precision
-    varg <- newName "arg"
-    pure
-        ( catMaybes [warg, parg, Just varg]
-        , appE
-              formatter
-              [|ArgSpec
-                    { flagSet = $(lift flags)
-                    , width = $(wexp)
-                    , prec = $(pexp)
-                    , value = $(varE varg)
-                    }|])
-  where
-    extractArgs n =
-        case n of
-            Just Need -> do
-                a <- newName "arg"
-                pure (Just a, [|Just $(varE a)|])
-            Just (Given n) -> pure (Nothing, [|Just $(litE $ integerL n)|])
-            Nothing -> pure (Nothing, [|Nothing|])
-    formatter =
-        case spec of
-            x
-                | x `elem` "id" -> [|formatDec|]
-            'u' -> [|formatNat|]
-            'o' -> [|formatOct|]
-            'x' -> [|formatHex|]
-            'X' -> [|formatHexUpper|]
-            's' -> [|formatStr|]
-            'f' -> [|formatFloat|]
-            'F' -> [|formatFloat|]
-            'a' -> [|formatHexFloat|]
-            'A' -> [|formatHexFloatUpper|]
-            'e' -> [|formatSci|]
-            'E' -> [|formatSciUpper|]
-            'g' -> [|formatG|]
-            'G' -> [|formatGUpper|]
-            'p' -> [|formatPtr|]
-            'c' -> [|formatChar|]
-            '?' -> [|formatShowable|]
-            _ -> error "???"
diff --git a/src/Text/Printf/TH/Parser.hs b/src/Text/Printf/TH/Parser.hs
deleted file mode 100644
--- a/src/Text/Printf/TH/Parser.hs
+++ /dev/null
@@ -1,51 +0,0 @@
-{-# Language NoMonomorphismRestriction #-}
-
-module Text.Printf.TH.Parser where
-
-import Control.Applicative
-import Data.CharSet (fromList)
-import Text.Trifecta
-import qualified Data.Set as S
-
-import Text.Printf.TH.Types
-
-flagSet = fromList "-+ #0"
-
-specSet = fromList "diuoxXfFeEaAgGpcs?"
-
-parseFmtStr = do
-    atoms <-
-        many $
-        choice [Str "%" <$ 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 = 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 <- optional $ choice [Given <$> natural, Need <$ char '*'] <?> "width"
-    precision <-
-        optional $
-        (do char '.'
-            choice [Given <$> natural, Need <$ char '*']) <?>
-        "precision"
-    spec <- oneOfSet specSet <?> "valid specifier"
-    pure $ FormatArg flags width precision spec
diff --git a/src/Text/Printf/TH/Printer.hs b/src/Text/Printf/TH/Printer.hs
deleted file mode 100644
--- a/src/Text/Printf/TH/Printer.hs
+++ /dev/null
@@ -1,216 +0,0 @@
-{-# Language RecordWildCards #-}
-{-# Language DefaultSignatures #-}
-{-# Language OverloadedStrings #-}
-{-# Language NamedFieldPuns #-}
-{-# Language FlexibleContexts #-}
-{-# Language FlexibleInstances #-}
-{-# Language TypeSynonymInstances #-}
-{-# Language TypeFamilies #-}
-
-module Text.Printf.TH.Printer where
-
-import Control.Monad.Fix
-import Data.Monoid
-import Data.String
-import Foreign.Ptr
-import Foreign.Storable
-import Numeric.Natural
-import Text.ParserCombinators.ReadP (readP_to_S)
-import Text.Printf.TH.Types hiding (width)
-import Text.Read.Lex
-
-class (IsString a, Monoid a) =>
-      Printer a
-    where
-    type Output a
-    string :: String -> a
-    cons :: Char -> a -> a
-    cons c = (formatChar' c <>)
-    rjust :: Char -> Int -> a -> a
-    ljust :: Int -> a -> a
-    output :: proxy a -> a -> Output a
-    formatChar' :: Char -> a
-    formatDec' :: Integral i => i -> a
-    formatOct' :: Integral i => i -> a
-    formatHex' :: Integral i => i -> a
-    formatHexUpper' :: Integral i => i -> a
-    formatFloat' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
-    formatHexFloat' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
-    formatHexFloatUpper' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
-    formatSci' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
-    formatSciUpper' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
-    formatG' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
-    formatGUpper' :: RealFloat f => Maybe Int -> (f -> a, f -> a)
-
-data ArgSpec v = ArgSpec
-    { flagSet :: FlagSet
-    , width :: Maybe Int
-    , prec :: Maybe Int
-    , value :: v
-    }
-
-forcePrefix a@ArgSpec {flagSet} = a {flagSet = flagSet {prefixed = True}}
-
-forceSize n a@ArgSpec {flagSet} =
-    a {width = Just n, flagSet = flagSet {adjustment = Just ZeroPadded}}
-
-forceNoZero a@ArgSpec {flagSet} =
-    a {flagSet = flagSet {adjustment = noZero (adjustment flagSet)}}
-  where
-    noZero (Just ZeroPadded) = Nothing
-    noZero x = x
-
-data Direction
-    = Leftward
-    | Rightward
-    deriving (Show)
-
-data Pad
-    = Space
-    | Zero
-    deriving (Eq, Show)
-
-data Val v = Val
-    { valLit :: v
-    , valWidth :: Maybe Int
-    , valPrefix :: Maybe (Int, v)
-    , valSign :: Maybe (Int, v)
-    , valPad :: Pad
-    , valDirection :: Direction
-    } deriving (Show)
-
-valOf x =
-    Val
-        { valLit = x
-        , valWidth = Nothing
-        , valPrefix = Nothing
-        , valSign = Nothing
-        , valPad = Space
-        , valDirection = Rightward
-        }
-
-valSign' = maybe mempty snd . valSign
-
-valPrefix' = maybe mempty snd . valPrefix
-
-setSign x v = v {valSign = Just $ fmap literal x}
-
-setPrefix x v = v {valPrefix = Just $ fmap literal x}
-
-setRightAligned v = v {valDirection = Rightward}
-
-setLeftAligned v = v {valDirection = Leftward}
-
-setWidth n v = v {valWidth = Just n}
-
-setWidth' n v = v {valWidth = n}
-
-setZero v = v {valPad = Zero}
-
-instance Functor ArgSpec where
-    fmap f a@(ArgSpec {value}) = a {value = f value}
-
-adjust (ArgSpec flags width _ _) =
-    setWidth' width .
-    case adjustment flags of
-        Nothing -> id
-        Just LeftJustified -> setLeftAligned
-        Just ZeroPadded -> setZero
-
-helper :: (Num a, Eq a, Printer p) => (a -> p) -> String -> ArgSpec a -> Val p
-helper f pref spec = adjustAndSign pref spec $ valOf $ f (abs $ value spec)
-
-adjustAndSign :: (Num n, Printer a, Eq n) => String -> ArgSpec n -> Val a -> Val a
-adjustAndSign pref (ArgSpec flags width _ num) =
-    adj . setWidth' width . sign flags num . prefix pref flags num
-  where
-    adj =
-        case adjustment flags of
-            Nothing -> id
-            Just LeftJustified -> setLeftAligned
-            Just ZeroPadded -> setZero
-
-prefix _ _ 0 = id
-prefix s flags _
-    | prefixed flags = setPrefix (length s, s)
-    | otherwise = id
-
-sign flags n
-    | signum n == -1 = setSign (1, "-")
-    | spaced flags = setSign (1, " ")
-    | signed flags = setSign (1, "+")
-    | otherwise = id
-
-formatDec = helper formatDec' ""
-
-formatOct = helper formatOct' "0"
-
-formatHex = helper formatHex' "0x"
-
-formatPtr =
-    helper formatHex' "0x" .
-    fmap (`minusPtr` nullPtr) . forcePrefix . forceSize (sizeOf nullPtr * 2)
-
-formatHexUpper = helper formatHexUpper' "0X"
-
-helper' spec pair = helper'' spec pair ""
-
-helper'' spec pair pref
-    | prefixed (flagSet spec) = helper (snd pair) pref spec
-    | otherwise = helper (fst pair) pref spec
-
-formatFloat spec = helper' spec (formatFloat' (prec spec))
-
-formatSci spec = helper' spec (formatSci' (prec spec))
-
-formatSciUpper spec = helper' spec (formatSciUpper' (prec spec))
-
-formatHexFloat spec
-    | isNaN x = helper (const "NaN") "" $ forceNoZero spec
-    | isInfinite x = helper (const "Infinity") "" $ forceNoZero spec
-    | otherwise = helper'' (forcePrefix spec) (formatHexFloat' (prec spec)) "0x"
-  where
-    x = value spec
-
-formatHexFloatUpper spec
-    | isNaN x = helper (const "NaN") "" $ forceNoZero spec
-    | isInfinite x = helper (const "Infinity") "" $ forceNoZero spec
-    | otherwise = helper'' (forcePrefix spec) (formatHexFloatUpper' (prec spec)) "0X"
-  where
-    x = value spec
-
-formatG spec = helper' spec (formatG' (prec spec))
-
-formatGUpper spec = helper' spec (formatGUpper' (prec spec))
-
-formatNat = formatDec . fmap (fromIntegral :: Natural -> Integer)
-
-fOne v@(Val {valWidth = Nothing, ..}) = mconcat [valSign' v, valPrefix' v, valLit]
-fOne v@(Val {valWidth = Just n, valDirection = Rightward, ..}) =
-    if valPad == Zero
-        then mconcat
-                 [ maybe mempty snd valSign
-                 , maybe mempty snd valPrefix
-                 , rjust '0' (n - extra) valLit
-                 ]
-        else rjust ' ' n $ fOne (v {valWidth = Nothing})
-  where
-    extra = maybe 0 fst valPrefix + maybe 0 fst valSign
-fOne v@(Val {valWidth = Just n, valDirection = Leftward, ..}) =
-    ljust n $ fOne (v {valWidth = Nothing})
-
-finalize p = foldMap (output p . fOne)
-
-formatStr spec = adjust spec $ valOf $ literal (value spec)
-
-formatChar spec = adjust spec $ valOf $ formatChar' (value spec)
-
-literal x
-    | '\\' `elem` x =
-        (`fix` x) $ \f s ->
-            case readP_to_S lexChar s of
-                ((c, rest):_) -> cons c (f rest)
-                [] -> mempty
-    | otherwise = string x
-
-formatShowable spec = adjust spec $ valOf $ literal (show $ value spec)
diff --git a/src/Text/Printf/TH/Printer/String.hs b/src/Text/Printf/TH/Printer/String.hs
deleted file mode 100644
--- a/src/Text/Printf/TH/Printer/String.hs
+++ /dev/null
@@ -1,53 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-{-# Language FlexibleContexts #-}
-{-# Language RecordWildCards #-}
-{-# Language TypeSynonymInstances #-}
-{-# Language TypeFamilies #-}
-{-# Language FlexibleInstances #-}
-
-module Text.Printf.TH.Printer.String where
-
-import NumericUtils
-import Text.Printf.TH.Printer
-
-instance Printer String where
-    type Output String = String
-    string = id
-    formatChar' = pure
-    cons = (:)
-    output _ = id
-    rjust c n s
-        | diff <= 0 = s
-        | otherwise = replicate diff c ++ s
-      where
-        diff = fromIntegral n - length s
-    ljust n s
-        | diff <= 0 = s
-        | otherwise = s ++ replicate diff ' '
-      where
-        diff = fromIntegral n - length s
-    formatDec' = showIntAtBase 10 intToDigit
-    formatOct' = showIntAtBase 8 intToDigit
-    formatHex' = showIntAtBase 16 intToDigit
-    formatHexUpper' = showIntAtBase 16 intToDigitUpper
-    formatFloat' p =
-        ( formatRealFloatAlt FFFixed (fromIntegral <$> p) False False
-        , formatRealFloatAlt FFFixed (fromIntegral <$> p) True False)
-    formatHexFloat' p =
-        ( formatFloatHex (fromIntegral <$> p) False False
-        , formatFloatHex (fromIntegral <$> p) True False)
-    formatHexFloatUpper' p =
-        ( formatFloatHex (fromIntegral <$> p) False True
-        , formatFloatHex (fromIntegral <$> p) True True)
-    formatSci' p =
-        ( formatRealFloatAlt FFExponent (fromIntegral <$> p) False False
-        , formatRealFloatAlt FFExponent (fromIntegral <$> p) True False)
-    formatSciUpper' p =
-        ( formatRealFloatAlt FFExponent (fromIntegral <$> p) False True
-        , formatRealFloatAlt FFExponent (fromIntegral <$> p) True True)
-    formatG' p =
-        ( formatRealFloatAlt FFGeneric (fromIntegral <$> p) False False
-        , formatRealFloatAlt FFGeneric (fromIntegral <$> p) True False)
-    formatGUpper' p =
-        ( formatRealFloatAlt FFGeneric (fromIntegral <$> p) False True
-        , formatRealFloatAlt FFGeneric (fromIntegral <$> p) True True)
diff --git a/src/Text/Printf/TH/Types.hs b/src/Text/Printf/TH/Types.hs
deleted file mode 100644
--- a/src/Text/Printf/TH/Types.hs
+++ /dev/null
@@ -1,84 +0,0 @@
-{-# Language TemplateHaskell #-}
-{-# Language NamedFieldPuns #-}
-
-module Text.Printf.TH.Types where
-
-import qualified Data.Set as S
-import Data.Set (Set)
-import Language.Haskell.TH.Syntax
-
-data Atom
-    = Arg FormatArg
-    | Str String
-    deriving (Show)
-
-data Flag
-    = FlagLJust
-    | FlagSigned
-    | FlagSpaced
-    | FlagPrefixed
-    | FlagZeroPadded
-    deriving (Show, Eq, Ord)
-
-adjustmentFlags = S.fromList [FlagLJust, FlagZeroPadded]
-
-data Adjustment
-    = LeftJustified
-    | ZeroPadded
-    deriving (Show)
-
-data MaySpecify
-    = Given Integer
-    | Need
-    deriving (Show)
-
-data FormatArg = FormatArg
-    { flags :: FlagSet
-    , width :: Maybe MaySpecify
-    , precision :: Maybe MaySpecify
-    , spec :: Char
-    } deriving (Show)
-
-type FormatStr = [Atom]
-
-instance Lift FlagSet where
-    lift (FlagSet a s b p) =
-        [|FlagSet
-              { adjustment = $(a')
-              , signed = $(lift s)
-              , spaced = $(lift b)
-              , prefixed = $(lift p)
-              }|]
-      where
-        a' =
-            case a of
-                Just LeftJustified -> [|Just LeftJustified|]
-                Just ZeroPadded -> [|Just ZeroPadded|]
-                Nothing -> [|Nothing|]
-
-data FlagSet = FlagSet
-    { adjustment :: Maybe Adjustment
-    , signed :: Bool
-    , spaced :: Bool
-    , prefixed :: Bool
-    } deriving (Show)
-
-toFlagSet :: Set Flag -> FlagSet
-toFlagSet fs
-    | S.size (S.intersection fs adjustmentFlags) > 1 =
-        error
-            "Error: multiple adjustment flags specified; you can only have one of '-', '0', ' '"
-    | spaced set && signed set = error "'+' and ' ' specifiers cannot be used together"
-    | otherwise = 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
-            }
diff --git a/tests/Codegen.hs b/tests/Codegen.hs
deleted file mode 100644
--- a/tests/Codegen.hs
+++ /dev/null
@@ -1,120 +0,0 @@
-{-# Language TemplateHaskell #-}
-
-module Codegen
-    ( gen
-    , str
-    ) where
-
-import Language.Haskell.TH.Lib
-import Language.Haskell.TH.Quote
-import Language.Haskell.TH.Syntax (Name)
-import Test.HUnit.Base
-import Test.Hspec
-import Test.QuickCheck
-
-str :: Gen String
-str = do
-    n <- elements [7 .. 20]
-    vectorOf n (elements $ ['A' .. 'Z'] ++ ['a' .. 'z'])
-
-gen :: String -> Name -> QuasiQuoter -> ExpQ
-gen name f quoter =
-    [|describe $(stringE name) $ do
-          describe "string" $ do
-              it "basic" $ do
-                  $(q "\\1234") @?= "\1234"
-                  $(q "\12\&34") @?= "\12\&34"
-                  $(q "%s") "foo" @?= "foo"
-                  $(q "%10s") "foo" @?= "       foo"
-                  $(q "%10s") "fübar" @?= $(varE f) "     fübar"
-                  $(q "%-10s") "foo" @?= "foo       "
-                  $(q "%010s") "foo" @?= "0000000foo"
-              it "precision" $ forAll str $ \n -> $(q "%.10s") n == $(q "%s") n
-              it "prefix" $ forAll str $ \n -> $(q "%#s") n == $(q "%s") n
-              it "words with format" $ do
-                  $(q "This is a string: %10s. It's nice") "foobar" @?=
-                      "This is a string:     foobar. It's nice"
-          describe "char" $ do
-              it "basic" $ do
-                  $(q "%c") 'U' @?= "U"
-                  $(q "%c") '\65210' @?= $(varE f) "\65210"
-                  $(q "%5c") '\65210' @?= $(varE f) "    \65210"
-          describe "decimal" $ do
-              it "basic" $ do
-                  $(q "%d") 20 @?= "20"
-                  $(q "%d") (negate 10) @?= "-10"
-                  $(q "%10d") 20 @?= "        20"
-                  $(q "%10d") (negate 10) @?= "       -10"
-                  $(q "%*d") 10 20 @?= "        20"
-                  $(q "%*d") 10 (negate 10) @?= "       -10"
-                  $(q "%010d") 20 @?= "0000000020"
-                  $(q "%010d") (negate 10) @?= "-000000010"
-                  $(q "% d") 20 @?= " 20"
-                  $(q "% d") (negate 10) @?= "-10"
-                  $(q "%+d") 20 @?= "+20"
-                  $(q "%+10d") 20 @?= "       +20"
-                  $(q "%-10d") 20 @?= "20        "
-                  $(q "%-10d") (negate 10) @?= "-10       "
-                  $(q "%+-10d") 20 @?= "+20       "
-          describe "unsigned" $ do it "basic" $ do $(q "%u") 20 @?= "20"
-          describe "octal" $ do
-              it "basic" $ do
-                  $(q "%o") 1500 @?= "2734"
-                  $(q "%o") (negate 1500) @?= "-2734"
-              it "prefix" $ do
-                  $(q "%#o") 1500 @?= "02734"
-                  $(q "%#o") 0 @?= "0"
-                  $(q "%#o") (negate 1500) @?= "-02734"
-                  $(q "%#010o") 1500 @?= "0000002734"
-                  $(q "%#010o") (negate 1500) @?= "-000002734"
-                  $(q "%#-10o") (negate 1500) @?= "-02734    "
-                  $(q "%#-1o") (negate 1500) @?= "-02734"
-          describe "hex" $ do
-              it "basic" $ do
-                  $(q "%x") 12513024 @?= "beef00"
-                  $(q "%X") 12513024 @?= "BEEF00"
-              it "prefix" $ do
-                  $(q "%#x") 12513024 @?= "0xbeef00"
-                  $(q "%#X") 12513024 @?= "0XBEEF00"
-                  $(q "%#15x") 12513024 @?= "       0xbeef00"
-                  $(q "%#015x") 12513024 @?= "0x0000000beef00"
-          describe "float" $ do
-              it "basic" $ do
-                  $(q "%f") 1234.56 @?= "1234.56"
-                  $(q "%f") (negate 1234.56) @?= "-1234.56"
-              it "precision" $ do
-                  $(q "%.5f") 1234.56 @?= "1234.56000"
-                  $(q "%.1f") 1234.56 @?= "1234.6"
-                  $(q "%.0f") 1234.56 @?= "1235"
-              it "prefix flag" $ do $(q "%#.0f") 1234.56 @?= "1235."
-          describe "scientific" $ do
-              it "basic" $ do
-                  $(q "%e") 1234.56 @?= "1.23456e3"
-                  $(q "%e") (negate 1234.56) @?= "-1.23456e3"
-                  $(q "%e") 0.00035 @?= "3.5e-4"
-                  $(q "%E") 1234.56 @?= "1.23456E3"
-              it "precision" $ do
-                  $(q "%.5e") 12.34 @?= "1.23400e1"
-                  $(q "%.0e") 12.34 @?= "1e1"
-                  $(q "%#.0e") 12.34 @?= "1.e1"
-                  $(q "%15.5e") 12.34 @?= "      1.23400e1"
-                  $(q "%+015.5e") 12.34 @?= "+000001.23400e1"
-                  $(q "%+-15.5e") 12.34 @?= "+1.23400e1     "
-          describe "g-format" $ do
-              it "basic" $ do
-                  $(q "%g") 1234.56 @?= "1234.56"
-                  $(q "%g") 123456789.876 @?= "123456789.876"
-                  $(q "%G") 123456789.876 @?= "123456789.876"
-                  $(q "%.3g") 1234.56 @?= "1.235e3"
-                  $(q "%.3g") 123456789.876 @?= "1.235e8"
-                  $(q "%.3G") 123456789.876 @?= "1.235E8"
-                  $(q "%#g") 1234.56 @?= "1234.56"
-          describe "hexadecimal floating point" $ do
-              it "basic" $ do
-                  $(q "%a") 0.857421875 @?= "0x1.b7p-1"
-                  $(q "%A") 3.1415926 @?= "0X1.921FB4D12D84AP+1"
-                  $(q "%.3a") 0.7576 @?= "0x1.83ep-1"
-                  $(q "%.3a") 1.999999999 @?= "0x2.000p+0"
-                  $(q "%015.3a") 0.7576 @?= "0x000001.83ep-1"|]
-  where
-    q = quoteExp quoter
diff --git a/tests/GeneratedSpec.hs b/tests/GeneratedSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/GeneratedSpec.hs
@@ -0,0 +1,452 @@
+-- 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
--- a/tests/format.hs
+++ b/tests/format.hs
@@ -1,18 +1,42 @@
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-{-# Language CPP #-}
-{-# Language OverloadedStrings #-}
-{-# Language TemplateHaskell #-}
-{-# Language QuasiQuotes #-}
+{-# OPTIONS_GHC -Wwarn #-}
+{-# LANGUAGE QuasiQuotes #-}
 
-module Main (main) where
+module Main where
 
-import Codegen
-import qualified Data.Text as T
+import Foreign.Ptr
+import GeneratedSpec
+import Language.Haskell.Printf
+import Test.HUnit
 import Test.Hspec
-import Text.Printf.TH
 
 main :: IO ()
 main =
-    hspec $ do
-        $(gen "String quoter" 'id s)
-        $(gen "Text quoter" 'T.pack st)
+    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/th-printf.cabal b/th-printf.cabal
--- a/th-printf.cabal
+++ b/th-printf.cabal
@@ -1,63 +1,82 @@
-name:          th-printf
-version:       0.5.1
-synopsis:      Compile-time printf
-description:   Quasiquoters for printf: string, bytestring, text.
-license:       MIT
-license-file:  LICENSE
-author:        Jude Taylor
-maintainer:    me@jude.xy
-category:      Text
-homepage:      https://github.com/pikajude/th-printf
-build-type:    Simple
 cabal-version: >= 1.10
 
+-- This file has been generated from package.yaml by hpack version 0.29.6.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 02f18fcc2963d061d9c5bff9a6cd352518b69cf49131279a887e54cc33c0ea3e
+
+name:           th-printf
+version:        0.6.0
+synopsis:       Quasiquoters for printf
+description:    Quasiquoters for printf: string, bytestring, text.
+category:       Text
+homepage:       https://github.com/pikajude/th-printf#readme
+bug-reports:    https://github.com/pikajude/th-printf/issues
+author:         Jude Taylor
+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
+build-type:     Simple
+
 source-repository head
-  type:     git
-  location: git://github.com/pikajude/th-printf.git
+  type: git
+  location: https://github.com/pikajude/th-printf
 
-library
-  exposed-modules:    Text.Printf.TH
-  other-modules:      NumericUtils
-                      Text.Printf.TH.Parser
-                      Text.Printf.TH.Printer
-                      Text.Printf.TH.Printer.String
-                      Text.Printf.TH.Types
-  hs-source-dirs:     src
-  build-depends:      base             >= 4.8 && < 5
-                    , ansi-wl-pprint
-                    , attoparsec
-                    , charset
-                    , containers
-                    , template-haskell
-                    , text
-                    , transformers
-                    , trifecta
-                    , utf8-string
-  default-language:   Haskell2010
-  default-extensions: NoMonomorphismRestriction
-  ghc-options:        -Wall -fno-warn-missing-signatures -fno-warn-name-shadowing
-                      -fno-warn-unused-do-bind
+flag werror
+  description: Build with -Werror
+  manual: True
+  default: False
 
-test-suite format
-  type:             exitcode-stdio-1.0
-  main-is:          format.hs
-  other-modules:    Codegen
-  hs-source-dirs:   tests
-  build-depends:    base
-                  , HUnit
-                  , QuickCheck
-                  , bytestring
-                  , hspec
-                  , template-haskell
-                  , text
-                  , th-printf
+library
+  exposed-modules:
+      Language.Haskell.Printf
+      Language.Haskell.Printf.Lib
+  other-modules:
+      Language.Haskell.Printf.Geometry
+      Language.Haskell.Printf.Printers
+      Language.Haskell.PrintfArg
+      NumUtils
+      StrUtils
+      Parser
+      Parser.Types
+      Paths_th_printf
+  hs-source-dirs:
+      src
+      parser
+  ghc-options: -Wall
+  build-depends:
+      base ==4.*
+    , charset
+    , containers
+    , microlens-platform
+    , mtl
+    , parsec
+    , semigroups
+    , template-haskell
+    , th-lift
+    , transformers
+  if flag(werror)
+    ghc-options: -Werror
   default-language: Haskell2010
-  ghc-options:      -Wall -fno-warn-type-defaults
 
-benchmark perf
-  type:             exitcode-stdio-1.0
-  main-is:          benchmark.hs
-  hs-source-dirs:   benchmark
-  build-depends:    base, criterion, text, th-printf
+test-suite format
+  type: exitcode-stdio-1.0
+  main-is: format.hs
+  other-modules:
+      GeneratedSpec
+      Paths_th_printf
+  hs-source-dirs:
+      tests
+  ghc-options: -Wall
+  build-depends:
+      HUnit
+    , QuickCheck
+    , base ==4.*
+    , hspec
+    , template-haskell
+    , th-printf
+  if flag(werror)
+    ghc-options: -Werror
   default-language: Haskell2010
-  ghc-options:      -O2 -Wall -fno-warn-type-defaults
