diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2013 Mikhail Vorozhtsov
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, 
+  this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright 
+  notice, this list of conditions and the following disclaimer in the 
+  documentation and/or other materials provided with the distribution.
+- Neither the names of the copyright owners nor the names of the 
+  contributors may be used to endorse or promote products derived 
+  from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Text/Printer.hs b/src/Text/Printer.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Printer.hs
@@ -0,0 +1,821 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE UnicodeSyntax #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+-- | Monoids with a homomorphism from 'String' to themselves.
+module Text.Printer
+  (
+  -- * The class
+     Printer(..)
+  -- * Builders
+  , StringBuilder(..)
+  , buildString
+  , AsciiBuilder(..)
+  , buildAscii
+  , Utf8Builder(..)
+  , buildUtf8
+  -- * Combinators
+  , (<>)
+  , hcat
+  , fcat
+  , separate
+  , (<+>)
+  , hsep
+  , fsep
+  , list
+  , parens
+  , brackets
+  , braces
+  , angles
+  , squotes
+  , dquotes
+  , punctuateL
+  , punctuateR
+  -- * Number printers
+  , unsignedBinary
+  , unsignedOctal
+  , unsignedUpHex
+  , unsignedLowHex
+  , unsignedDecimal
+  , binary'
+  , binary
+  , octal'
+  , octal
+  , lowHex'
+  , lowHex
+  , upHex'
+  , upHex
+  , decimal'
+  , decimal
+  -- * Multiline printers
+  , MultilinePrinter(..)
+  , lines
+  , newLine
+  , crlf
+  , LinePrinter(..)
+  , lfPrinter
+  , crlfPrinter
+  -- * Default printers
+  , Printable(..)
+  , defaultPrintList
+  , toString
+  ) where
+
+import Prelude hiding (foldr, foldr1, print, lines)
+import Data.Typeable (Typeable)
+import Data.Int
+import Data.Word
+import Data.Bits (Bits(..))
+import Data.Char (chr, ord)
+import Data.String (IsString(..))
+import Data.Monoid (Monoid(..), (<>))
+import Data.Foldable (Foldable(..), toList)
+import Data.Traversable (Traversable, mapAccumL, mapAccumR)
+import qualified Data.Text as TS
+import qualified Data.Text.Encoding as TS
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
+import qualified Data.Text.Lazy.Builder as TB
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as BS8
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Lazy.Char8 as BL8
+import qualified Data.ByteString.Lazy.Builder as BB
+import qualified Text.PrettyPrint as PP
+
+-- | Text monoid. 'string' must be equivalent to 'fromString' and be a monoid
+--   homomorphism, i.e. @'string' 'mempty' = 'mempty'@ and
+--   @'mappend' ('string' /x/) ('string' /y/) = 'string' ('mappend' /x/ /y/)@.
+--   Other operations must be monoid homomorphisms that are eqiuvalent (but
+--   possibly faster) to the composition of 'string' and the corresponding
+--   embedding, e.g. @'text' = 'string' . 'TS.unpack'@.
+class (IsString p, Monoid p) ⇒ Printer p where
+  -- | Print a character. @'char' /c/@ must be equivalent to
+  --   @'string' [/c/]@, but hopefully is faster.
+  char ∷ Char → p
+  char c = string [c]
+  {-# INLINE char #-}
+  -- | Print an ASCII character, can be faster than 'char'.
+  char7 ∷ Char → p
+  char7 = char
+  {-# INLINE char7 #-}
+  -- | Print a string.
+  string ∷ String → p
+  string = fromString
+  {-# INLINE string #-}
+  -- | Print an ASCII string, can be faster than 'string'.
+  string7 ∷ String → p
+  string7 = string
+  {-# INLINE string7 #-}
+  -- | Print a 'TS.Text'.
+  text ∷ TS.Text → p
+  text = string . TS.unpack
+  {-# INLINE text #-}
+  -- | Print a lazy 'TL.Text'.
+  lazyText ∷ TL.Text → p
+  lazyText = string . TL.unpack
+  {-# INLINE lazyText #-}
+  -- | Print an ASCII 'BS.ByteString'.
+  ascii ∷ BS.ByteString → p
+  ascii = string . BS8.unpack
+  {-# INLINE ascii #-}
+  -- | Print a lazy ASCII 'BL.ByteString'.
+  lazyAscii ∷ BL.ByteString → p
+  lazyAscii = string . BL8.unpack
+  {-# INLINE lazyAscii #-}
+  -- | Print a UTF-8 'BS.ByteString'.
+  utf8 ∷ BS.ByteString → p
+  utf8 = text . TS.decodeUtf8
+  {-# INLINE utf8 #-}
+  -- | Print a lazy UTF-8 'BL.ByteString'
+  lazyUtf8 ∷ BL.ByteString → p
+  lazyUtf8 = lazyText . TL.decodeUtf8
+  {-# INLINE lazyUtf8 #-}
+
+instance Printer String where
+
+-- | A simple string builder as used by 'Show'.
+newtype StringBuilder = StringBuilder { stringBuilder ∷ String → String }
+                        deriving (Typeable, Monoid)
+
+instance IsString StringBuilder where
+  fromString s = StringBuilder (s ++)
+  {-# INLINE fromString #-}
+
+instance Printer StringBuilder where
+  char c = StringBuilder (c :)
+  {-# INLINE char #-}
+
+buildString ∷ StringBuilder → String
+buildString b = stringBuilder b ""
+{-# INLINE buildString #-}
+
+instance Printer TB.Builder where
+  char = TB.singleton
+  {-# INLINE char #-}
+  text = TB.fromText
+  {-# INLINE text #-}
+  lazyText = TB.fromLazyText
+  {-# INLINE lazyText #-}
+
+-- | Use this builder when you are sure that only ASCII characters
+--   will get printed to it.
+newtype AsciiBuilder = AsciiBuilder { asciiBuilder ∷ BB.Builder }
+                       deriving (Typeable, Monoid)
+
+instance IsString AsciiBuilder where
+  fromString = AsciiBuilder . BB.string7
+  {-# INLINE fromString #-}
+
+instance Printer AsciiBuilder where
+  char = AsciiBuilder . BB.char7
+  {-# INLINE char #-}
+  ascii = AsciiBuilder . BB.byteString
+  {-# INLINE ascii #-}
+  lazyAscii = AsciiBuilder . BB.lazyByteString
+  {-# INLINE lazyAscii #-}
+  utf8 = AsciiBuilder . BB.byteString
+  {-# INLINE utf8 #-}
+  lazyUtf8 = AsciiBuilder . BB.lazyByteString
+  {-# INLINE lazyUtf8 #-}
+
+buildAscii ∷ AsciiBuilder → BL.ByteString
+buildAscii = BB.toLazyByteString . asciiBuilder
+{-# INLINE buildAscii #-}
+
+-- | UTF-8 lazy 'BL.ByteString' builder.
+newtype Utf8Builder = Utf8Builder { utf8Builder ∷ BB.Builder }
+                      deriving (Typeable, Monoid)
+
+instance IsString Utf8Builder where
+  fromString = Utf8Builder . BB.stringUtf8
+  {-# INLINE fromString #-}
+
+instance Printer Utf8Builder where
+  char = Utf8Builder . BB.charUtf8
+  {-# INLINE char #-}
+  char7 = Utf8Builder . BB.char7
+  {-# INLINE char7 #-}
+  string7 = Utf8Builder . BB.string7
+  {-# INLINE string7 #-}
+  text = Utf8Builder . BB.byteString . TS.encodeUtf8
+  {-# INLINE text #-}
+  lazyText = Utf8Builder . BB.lazyByteString . TL.encodeUtf8
+  {-# INLINE lazyText #-}
+  ascii = Utf8Builder . BB.byteString
+  {-# INLINE ascii #-}
+  lazyAscii = Utf8Builder . BB.lazyByteString
+  {-# INLINE lazyAscii #-}
+  utf8 = Utf8Builder . BB.byteString
+  {-# INLINE utf8 #-}
+  lazyUtf8 = Utf8Builder . BB.lazyByteString
+  {-# INLINE lazyUtf8 #-}
+
+buildUtf8 ∷ Utf8Builder → BL.ByteString
+buildUtf8 = BB.toLazyByteString . utf8Builder
+{-# INLINE buildUtf8 #-}
+
+instance Printer PP.Doc where
+  char = PP.char
+  {-# INLINE char #-}
+
+#if !MIN_VERSION_base(4,5,0)
+-- | An infix synonym for 'mappend'.
+(<>) ∷ Monoid m ⇒ m → m → m
+(<>) = mappend
+{-# INLINE (<>) #-}
+#endif
+
+-- | 'mconcat' for 'Foldable' data structures.
+hcat ∷ (Printer p, Foldable f) ⇒ f p → p
+hcat = fold
+{-# INLINE hcat #-}
+
+-- | Combine the items of a 'Foldable' data structure using the provided
+--   function. If the data structure is empty, 'mempty' is returned.
+fcat ∷ (Foldable f, Printer p) ⇒ (p → p → p) → f p → p
+fcat c f = case toList f of
+  [] → mempty
+  ps → foldr1 c ps
+{-# INLINABLE fcat #-}
+
+-- | Concatenate two 'Printer's with a separator between them.
+separate ∷ Printer p
+         ⇒ p -- ^ The separator
+         → p → p → p
+separate s x y = x <> s <> y
+{-# INLINE separate #-}
+
+infixr 6 <+>
+
+-- | Concatenate two 'Printer's with a space between them.
+(<+>) ∷ Printer p ⇒ p → p → p
+(<+>) = separate (char7 ' ')
+{-# INLINE (<+>) #-}
+
+-- | Concatenate the items of a 'Foldable' data structure
+--   with spaces between them.
+hsep ∷ (Printer p, Foldable f) ⇒ f p → p
+hsep = fcat (<+>)
+{-# INLINE hsep #-}
+
+-- | A shorthand for @'fcat' . 'separate'@.
+fsep ∷ (Foldable f, Printer p) ⇒ p → f p → p
+fsep = fcat . separate
+{-# INLINE fsep #-}
+
+-- | Concatenate the items of a 'Foldable' data structure with commas
+--   between them.
+--
+-- @
+--   'list' = 'fsep' ('char7' ',')
+-- @
+list ∷ (Foldable f, Printer p) ⇒ f p → p
+list = fsep (char7 ',')
+{-# INLINE list #-}
+
+-- | Enclose a 'Printer' with parentheses.
+parens ∷ Printer p ⇒ p → p
+parens p = char7 '(' <> p <> char7 ')'
+{-# INLINE parens #-}
+
+-- | Enclose a 'Printer' with square brackets.
+brackets ∷ Printer p ⇒ p → p
+brackets p = char7 '[' <> p <> char7 ']'
+{-# INLINE brackets #-}
+
+-- | Enclose a 'Printer' with curly braces.
+braces ∷ Printer p ⇒ p → p
+braces p = char7 '{' <> p <> char7 '}'
+{-# INLINE braces #-}
+
+-- | Enclose a 'Printer' with angle brackets.
+angles ∷ Printer p ⇒ p → p
+angles p = char7 '<' <> p <> char7 '>'
+{-# INLINE angles #-}
+
+-- | Enclose a 'Printer' with single quotes.
+squotes ∷ Printer p ⇒ p → p
+squotes p = char7 '\'' <> p <> char7 '\''
+{-# INLINE squotes #-}
+
+-- | Enclose a 'Printer' with double quotes.
+dquotes ∷ Printer p ⇒ p → p
+dquotes p  = char7 '\"' <> p <> char7 '\"'
+{-# INLINE dquotes #-}
+
+-- | Prepend all but the first element of a 'Traversable' with the
+--   provided value, e.g.
+--   @'punctuateL' /p/ [/x1/, /x2/, ..., /xN/] =
+--      [/x1/, /p/ '<>' /x2/, ..., /p/ '<>' /xN/]@
+punctuateL ∷ (Traversable t, Printer p) ⇒ p → t p → t p
+punctuateL p =
+  snd . mapAccumL (\f a → if f then (False, a) else (False, p <> a)) True
+{-# INLINE punctuateL #-}
+
+-- | Append the provided value to all but the last element of a 'Traversable',
+--   e.g. @'punctuateR' /p/ [/x1/, ..., /xN-1/, /xN/] =
+--           [/x1/ '<>' /p/, ..., /xN-1/ '<>' /p/, /xN/]@
+punctuateR ∷ (Traversable t, Printer p) ⇒ p → t p → t p
+punctuateR p =
+  snd . mapAccumR (\l a → if l then (False, a) else (False, a <> p)) True
+{-# INLINE punctuateR #-}
+
+-- | Print an unsigned number in the binary numeral system.
+unsignedBinary ∷ (Num α, Bits α, Printer p) ⇒ α → p
+unsignedBinary = go (char7 '0')
+  where go p 0 = p
+        go _ m = go mempty (shiftR m 1) <> char7 c
+          where !c = if testBit m 0 then '1' else '0'
+{-# INLINABLE unsignedBinary #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Int → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Int8 → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Int16 → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Int32 → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Int64 → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Word → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Word8 → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Word16 → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Word32 → p #-}
+{-# SPECIALIZE unsignedBinary ∷ Printer p ⇒ Word64 → p #-}
+
+-- | Print an unsigned number in the octal numeral system.
+unsignedOctal ∷ (Num α, Bits α, Printer p) ⇒ α → p
+unsignedOctal = go (char7 '0')
+  where go p 0 = p
+        go _ m = go mempty (shiftR m 3) <> char7 c
+          where !r = (if testBit m 0 then 1 else 0)
+                   + (if testBit m 1 then 2 else 0)
+                   + (if testBit m 2 then 4 else 0)
+                !c = chr $ ord '0' + r
+{-# INLINABLE unsignedOctal #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Int → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Int8 → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Int16 → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Int32 → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Int64 → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Word → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Word8 → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Word16 → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Word32 → p #-}
+{-# SPECIALIZE unsignedOctal ∷ Printer p ⇒ Word64 → p #-}
+
+-- | Print an unsigned number in the hexadecimal numeral system
+--   using lower case digits.
+unsignedLowHex ∷ (Num α, Bits α, Printer p) ⇒ α → p
+unsignedLowHex = go (char7 '0')
+  where go p 0 = p
+        go _ m = go mempty (shiftR m 4) <> char7 c
+          where !r = (if testBit m 0 then 1 else 0)
+                   + (if testBit m 1 then 2 else 0)
+                   + (if testBit m 2 then 4 else 0)
+                   + (if testBit m 3 then 8 else 0)
+                !c | r < 10    = chr $ ord '0' + r
+                   | otherwise = chr $ ord 'a' + r - 10
+{-# INLINABLE unsignedLowHex #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Int → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Int8 → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Int16 → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Int32 → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Int64 → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Word → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Word8 → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Word16 → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Word32 → p #-}
+{-# SPECIALIZE unsignedLowHex ∷ Printer p ⇒ Word64 → p #-}
+
+-- | Print an unsigned number in the hexadecimal numeral system
+--   using upper case digits.
+unsignedUpHex ∷ (Num α, Bits α, Printer p) ⇒ α → p
+unsignedUpHex = go (char7 '0')
+  where go p 0 = p
+        go _ m = go mempty (shiftR m 4) <> char7 c
+          where !r = (if testBit m 0 then 1 else 0)
+                   + (if testBit m 1 then 2 else 0)
+                   + (if testBit m 2 then 4 else 0)
+                   + (if testBit m 3 then 8 else 0)
+                !c | r < 10    = chr $ ord '0' + r
+                   | otherwise = chr $ ord 'A' + r - 10
+{-# INLINABLE unsignedUpHex #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Int → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Int8 → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Int16 → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Int32 → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Int64 → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Word → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Word8 → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Word16 → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Word32 → p #-}
+{-# SPECIALIZE unsignedUpHex ∷ Printer p ⇒ Word64 → p #-}
+
+-- | Print an unsigned number in the decimal numeral system.
+unsignedDecimal ∷ (Integral α, Printer p) ⇒ α → p
+unsignedDecimal = go (char7 '0')
+  where go p 0 = p
+        go _ m = go mempty q <> char7 c
+          where (q, r) = quotRem m 10
+                !c = chr $ ord '0' + fromIntegral r
+{-# INLINABLE unsignedDecimal #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Int → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Int8 → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Int16 → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Int32 → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Int64 → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Word → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Word8 → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Word16 → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Word32 → p #-}
+{-# SPECIALIZE unsignedDecimal ∷ Printer p ⇒ Word64 → p #-}
+
+-- | Print a number in the binary numeral system.
+binary' ∷ (Ord α, Num α, Bits α, Printer p)
+        ⇒ p -- ^ Prefix for negative values
+        → p -- ^ Prefix for the zero
+        → p -- ^ Prefix for positive values
+        → α → p
+binary' neg z pos n = case compare n 0 of
+    LT → case testBit n 0 of
+           True → go neg (shiftR (negate n) 1) <> char7 '1'
+           False → go neg (negate $ shiftR n 1) <> char7 '0'
+    EQ → z <> char7 '0'
+    GT → go pos (shiftR n 1) <> char7 c
+      where !c = if testBit n 0 then '1' else '0'
+  where go p 0 = p
+        go p m = go p (shiftR m 1) <> char7 c
+          where !c = if testBit m 0 then '1' else '0'
+{-# INLINABLE binary' #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Int → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Int8 → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Int16 → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Int32 → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Int64 → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Word → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Word8 → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Word16 → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Word32 → p #-}
+{-# SPECIALIZE binary' ∷ Printer p ⇒ p → p → p → Word64 → p #-}
+
+-- | Print a number in the binary numeral system. Negative values
+--   are prefixed with \"-0b\", postive values are prefixed with \"0b\".
+binary ∷ (Ord α, Num α, Bits α, Printer p) ⇒ α → p
+binary = binary' (string7 "-0b") mempty (string7 "0b")
+{-# INLINE binary #-}
+
+-- | Print a number in the octal numeral system.
+octal' ∷ (Ord α, Num α, Bits α, Printer p)
+       ⇒ p -- ^ Prefix for negative values
+       → p -- ^ Prefix for the zero
+       → p -- ^ Prefix for positive values
+       → α → p
+octal' neg z pos n = case compare n 0 of
+    LT → case testBit n 0 of
+           True → go neg (shiftR (negate n) 3) <> char7 c
+             where !r = 7
+                      - (if testBit n 1 then 2 else 0)
+                      - (if testBit n 2 then 4 else 0)
+                   !c = chr $ ord '0' + r
+           False → case n > negate (bit 3) of
+               True → neg <> char7 c 
+               False → go neg m <> char7 c
+                 where m | v == 0    = negate $ shiftR n 3
+                         | otherwise = complement $ shiftR n 3
+             where !v = (if testBit n 1 then 2 else 0)
+                      + (if testBit n 2 then 4 else 0)
+                   !r = (8 - v) .&. 7
+                   !c = chr $ ord '0' + r
+    EQ → z <> char7 '0'
+    GT → go pos (shiftR n 3) <> char7 c
+      where !r = (if testBit n 0 then 1 else 0)
+               + (if testBit n 1 then 2 else 0)
+               + (if testBit n 2 then 4 else 0)
+            !c = chr $ ord '0' + r
+  where go p 0 = p
+        go p m = go p (shiftR m 3) <> char7 c
+          where !r = (if testBit m 0 then 1 else 0)
+                   + (if testBit m 1 then 2 else 0)
+                   + (if testBit m 2 then 4 else 0)
+                !c = chr $ ord '0' + r
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Int → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Int8 → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Int16 → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Int32 → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Int64 → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Word → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Word8 → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Word16 → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Word32 → p #-}
+{-# SPECIALIZE octal' ∷ Printer p ⇒ p → p → p → Word64 → p #-}
+
+-- | Print a number in the octal numeral system. Negative values
+--   are prefixed with \"-0o\", postive values are prefixed with \"0o\".
+octal ∷ (Ord α, Num α, Bits α, Printer p) ⇒ α → p
+octal = octal' (string7 "-0o") mempty (string7 "0o")
+{-# INLINE octal #-}
+
+hex' ∷ (Ord α, Num α, Bits α, Printer p) ⇒ Int → p → p → p → α → p
+hex' a neg z pos n = case compare n 0 of
+    LT → case testBit n 0 of
+      True → go neg (shiftR (negate n) 4) <> char7 c
+        where !r = 15
+                 - (if testBit n 1 then 2 else 0)
+                 - (if testBit n 2 then 4 else 0)
+                 - (if testBit n 3 then 8 else 0)
+              !c | r < 10    = chr $ ord '0' + r
+                 | otherwise = chr $ a + (r - 10) 
+      False → case n > negate (bit 4) of
+          True → neg <> char7 c 
+          False → go neg m <> char7 c
+            where m | v == 0    = negate $ shiftR n 4
+                    | otherwise = complement $ shiftR n 4
+        where !v = (if testBit n 1 then 2 else 0)
+                 + (if testBit n 2 then 4 else 0)
+                 + (if testBit n 3 then 8 else 0)
+              !r = (16 - v) .&. 15
+              !c | r < 10    = chr $ ord '0' + r
+                 | otherwise = chr $ a + (r - 10)
+    EQ → z <> char7 '0'
+    GT → go pos (shiftR n 4) <> char7 c
+      where !r = (if testBit n 0 then 1 else 0)
+               + (if testBit n 1 then 2 else 0)
+               + (if testBit n 2 then 4 else 0)
+               + (if testBit n 3 then 8 else 0)
+            !c | r < 10    = chr $ ord '0' + r
+               | otherwise = chr $ a + (r - 10)
+  where go p 0 = p
+        go p m = go p (shiftR m 4) <> char7 c
+          where !r = (if testBit m 0 then 1 else 0)
+                   + (if testBit m 1 then 2 else 0)
+                   + (if testBit m 2 then 4 else 0)
+                   + (if testBit m 3 then 8 else 0)
+                !c | r < 10    = chr $ ord '0' + r
+                   | otherwise = chr $ a + (r - 10)
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Int → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Int8 → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Int16 → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Int32 → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Int64 → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Word → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Word8 → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Word16 → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Word32 → p #-}
+{-# SPECIALIZE hex' ∷ Printer p ⇒ Int → p → p → p → Word64 → p #-}
+
+-- | Print a number in the hexadecimal numeral system using lower case
+--   digits.
+lowHex' ∷ (Ord α, Num α, Bits α, Printer p)
+        ⇒ p -- ^ Prefix for negative values
+        → p -- ^ Prefix for the zero
+        → p -- ^ Prefix for postive values
+        → α → p
+lowHex' = hex' (ord 'a')
+{-# INLINE lowHex' #-}
+
+-- | Print a number in the hexadecimal numeral system using lower case
+--   digits. Negative values are prefixed with \"-0x\", positive values
+--   are prefixed with \"0x\".
+lowHex ∷ (Ord α, Num α, Bits α, Printer p) ⇒ α → p
+lowHex = lowHex' (string7 "-0x") mempty (string7 "0x")
+{-# INLINE lowHex #-}
+
+-- | Print a number in the hexadecimal numeral system using upper case
+--   digits.
+upHex' ∷ (Ord α, Num α, Bits α, Printer p)
+        ⇒ p -- ^ Prefix for negative values
+        → p -- ^ Prefix for the zero
+        → p -- ^ Prefix for postive values
+        → α → p
+upHex' = hex' (ord 'A')
+{-# INLINE upHex' #-}
+
+-- | Print a number in the hexadecimal numeral system using upper case
+--   digits. Negative values are prefixed with \"-0x\", positive values
+--   are prefixed with \"0x\".
+upHex ∷ (Ord α, Num α, Bits α, Printer p) ⇒ α → p
+upHex = upHex' (string7 "-0x") mempty (string7 "0x")
+{-# INLINE upHex #-}
+
+-- | Print a number in the decimal numeral system.
+decimal' ∷ (Integral α, Printer p)
+        ⇒ p -- ^ Prefix for negative values
+        → p -- ^ Prefix for the zero
+        → p -- ^ Prefix for postive values
+        → α → p
+decimal' neg z pos n = case compare n 0 of
+    LT → go neg q <> char7 c
+      where (q, r) = quotRem n (-10)
+            !c     = chr $ ord '0' - fromIntegral r
+    EQ → z <> char7 '0'
+    GT → go pos q <> char7 c
+      where (q, r) = quotRem n 10
+            !c = chr $ ord '0' + fromIntegral r
+  where go p 0 = p
+        go p m = go p q <> char7 c
+          where (q, r) = quotRem m 10
+                !c = chr $ ord '0' + fromIntegral r
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Int → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Int8 → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Int16 → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Int32 → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Int64 → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Word → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Word8 → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Word16 → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Word32 → p #-}
+{-# SPECIALIZE decimal' ∷ Printer p ⇒ p → p → p → Word64 → p #-}
+
+-- | Print a number in the decimal numeral system. Negative values
+--   are prefixed with \"-\".
+decimal ∷ (Integral α, Printer p) ⇒ α → p
+decimal = decimal' (char7 '-') mempty mempty
+{-# INLINE decimal #-}
+
+infixr 5 <->
+
+-- | Printers that can produce multiple lines of text.
+class Printer p ⇒ MultilinePrinter p where
+  -- | Combine two lines. Must be associative, i.e.
+  --   /x/ '<->' (/y/ '<->' /z/) = (/x/ '<->' /y/) '<->' /z/.
+  (<->) ∷ p → p → p
+
+instance MultilinePrinter PP.Doc where
+  (<->) = (PP.$+$)
+  {-# INLINE (<->) #-}
+
+-- | Combine the items of a 'Foldable' data structure with '<->'.
+lines ∷ (MultilinePrinter p, Foldable f) ⇒ f p → p
+lines = fcat (<->)
+{-# INLINE lines #-}
+
+-- | Print the LF character (/'\n'/).
+newLine ∷ Printer p ⇒ p
+newLine = char '\n'
+{-# INLINE newLine #-}
+
+-- | Print CR (/'\r'/) followed by LF (/'\n'/).
+crlf ∷ Printer p ⇒ p
+crlf = char '\r' <> char '\n'
+{-# INLINE crlf #-}
+
+-- | A multiline printer that combines lines with the provided function.
+newtype LinePrinter p = LinePrinter { linePrinter ∷ (p → p → p) → p }
+                        deriving Typeable
+
+instance IsString p ⇒ IsString (LinePrinter p) where
+  fromString = LinePrinter . const . fromString
+  {-# INLINE fromString #-}
+
+instance Monoid p ⇒ Monoid (LinePrinter p) where
+  mempty = LinePrinter $ const mempty
+  {-# INLINE mempty #-}
+  mappend x y = LinePrinter $ \l →
+                  mappend (linePrinter x l) (linePrinter y l)
+  {-# INLINE mappend #-}
+  mconcat xs = LinePrinter $ \l → mconcat (map (\x → linePrinter x l) xs)
+  {-# INLINE mconcat #-}
+
+instance Printer p ⇒ Printer (LinePrinter p) where
+  char = LinePrinter . const . char
+  {-# INLINE char #-}
+  char7 = LinePrinter . const . char7
+  {-# INLINE char7 #-}
+  string = LinePrinter . const . string
+  {-# INLINE string #-}
+  string7 = LinePrinter . const . string7
+  {-# INLINE string7 #-}
+  text = LinePrinter . const . text
+  {-# INLINE text #-}
+  lazyText = LinePrinter . const . lazyText
+  {-# INLINE lazyText #-}
+  ascii = LinePrinter . const . ascii
+  {-# INLINE ascii #-}
+  lazyAscii = LinePrinter . const . lazyAscii
+  {-# INLINE lazyAscii #-}
+  utf8 = LinePrinter . const . utf8
+  {-# INLINE utf8 #-}
+  lazyUtf8 = LinePrinter . const . lazyUtf8
+  {-# INLINE lazyUtf8 #-}
+  
+instance Printer p ⇒ MultilinePrinter (LinePrinter p) where
+  x <-> y = LinePrinter $ \l → l (linePrinter x l) (linePrinter y l)
+  {-# INLINE (<->) #-}
+
+-- | Separate lines with 'newLine'.
+lfPrinter ∷ Printer p ⇒ LinePrinter p → p
+lfPrinter p = linePrinter p (separate newLine)
+{-# INLINE lfPrinter #-}
+
+-- | Separate lines with 'crlf'.
+crlfPrinter ∷ Printer p ⇒ LinePrinter p → p
+crlfPrinter p = linePrinter p (separate crlf)
+{-# INLINE crlfPrinter #-}
+
+-- | The default printer for values of a type.
+class Printable α where
+  print ∷ Printer p ⇒ α → p
+  printList ∷ Printer p ⇒ [α] → p
+  printList = defaultPrintList
+  {-# INLINE printList #-}
+
+defaultPrintList ∷ (Printable α, Printer p) ⇒ [α] → p
+defaultPrintList = brackets . list . map print
+{-# INLINE defaultPrintList #-}
+
+instance Printable () where
+  print _ = string7 "()"
+  {-# INLINE print #-}
+
+instance (Printable α, Printable β) ⇒ Printable (α, β) where
+  print (a, b) = parens $ list [print a, print b]
+  {-# INLINE print #-}
+
+instance (Printable α, Printable β, Printable γ) ⇒ Printable (α, β, γ) where
+  print (a, b, c) = parens $ list [print a, print b, print c]
+  {-# INLINE print #-}
+
+instance (Printable α, Printable β, Printable γ, Printable δ)
+         ⇒ Printable (α, β, γ, δ) where
+  print (a, b, c, d) = parens $ list [print a, print b, print c, print d]
+  {-# INLINE print #-}
+
+instance Printable α ⇒ Printable [α] where
+  print = printList
+  {-# INLINE print #-}
+
+instance Printable Char where
+  print = char
+  {-# INLINE print #-}
+  printList = string
+  {-# INLINE printList #-}
+
+instance Printable TS.Text where
+  print = text
+  {-# INLINE print #-}
+
+instance Printable TL.Text where
+  print = lazyText
+  {-# INLINE print #-}
+
+instance Printable Integer where
+  print = decimal
+  {-# INLINE print #-}
+
+instance Printable Int where
+  print = decimal
+  {-# INLINE print #-}
+
+instance Printable Int8 where
+  print = decimal
+  {-# INLINE print #-}
+
+instance Printable Int16 where
+  print = decimal
+  {-# INLINE print #-}
+
+instance Printable Int32 where
+  print = decimal
+  {-# INLINE print #-}
+
+instance Printable Int64 where
+  print = decimal
+  {-# INLINE print #-}
+
+instance Printable Word where
+  print = unsignedDecimal
+  {-# INLINE print #-}
+
+instance Printable Word8 where
+  print = unsignedDecimal
+  {-# INLINE print #-}
+
+instance Printable Word16 where
+  print = unsignedDecimal
+  {-# INLINE print #-}
+
+instance Printable Word32 where
+  print = unsignedDecimal
+  {-# INLINE print #-}
+
+instance Printable Word64 where
+  print = unsignedDecimal
+  {-# INLINE print #-}
+
+instance Printable Float where
+  print = string7 . show
+  {-# INLINE print #-}
+
+instance Printable Double where
+  print = string7 . show
+  {-# INLINE print #-}
+
+-- | Print a 'Printable' value via 'StringBuilder', i.e.
+--   @'toString' = 'buildString' . 'print'@.
+toString ∷ Printable α ⇒ α → String
+toString = buildString . print
+{-# INLINE toString #-}
+
diff --git a/tests/Tests.hs b/tests/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Tests.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE UnicodeSyntax #-}
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+
+import Test.Framework (defaultMain)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+
+import Data.Word (Word)
+import Data.Char (intToDigit)
+import Numeric (showIntAtBase)
+import Text.Printf (printf)
+import Text.Printer
+
+main = defaultMain
+  [ testProperty "unsignedBinary" $ \w →
+      unsignedBinary w == showBinary (w ∷ Word)
+  , testProperty "unsignedOctal" $ \w →
+      unsignedOctal w == (printf "%o" (w ∷ Word) ∷ String)
+  , testProperty "unsignedDecimal" $ \w →
+      unsignedDecimal w == show (w ∷ Word)
+  , testProperty "unsignedLowHex" $ \w →
+      unsignedLowHex w == (printf "%x" (w ∷ Word) ∷ String)
+  , testProperty "unsignedUpHex" $ \w →
+      unsignedUpHex w == (printf "%X" (w ∷ Word) ∷ String)
+  , testProperty "binary" $ \i →
+      if (i ∷ Int) == 0
+      then binary i == "0"
+      else if i < 0
+           then binary i == "-0b" ++ showBinary (negate $ toInteger i)
+           else binary i == "0b" ++ showBinary i
+  , testProperty "octal" $ \i →
+      if (i ∷ Int) == 0
+      then octal i == "0"
+      else if i < 0
+           then octal i == (printf "-0o%o" (negate $ toInteger i) ∷ String)
+           else octal i == (printf "0o%o" i ∷ String)
+  , testProperty "lowHex" $ \i →
+      if (i ∷ Int) == 0
+      then lowHex i == "0"
+      else if i < 0
+           then lowHex i == (printf "-0x%x" (negate $ toInteger i) ∷ String)
+           else lowHex i == (printf "0x%x" i ∷ String)
+  , testProperty "upHex" $ \i →
+      if (i ∷ Int) == 0
+      then upHex i == "0"
+      else if i < 0
+           then upHex i == (printf "-0x%X" (negate $ toInteger i) ∷ String)
+           else upHex i == (printf "0x%X" i ∷ String)
+  , testProperty "decimal" $ \i →
+      decimal i == show (i ∷ Int)
+  ]
+
+showBinary i = showIntAtBase 2 intToDigit i ""
+
diff --git a/text-printer.cabal b/text-printer.cabal
new file mode 100644
--- /dev/null
+++ b/text-printer.cabal
@@ -0,0 +1,50 @@
+Name: text-printer
+Version: 0.1
+Category: Text
+Stability: experimental
+Synopsis: Abstract interface for text builders/printers.
+Description:
+  This package provides an interface for injecting text into monoids such as
+  builders and printers.
+
+Homepage: https://github.com/mvv/text-printer
+Bug-Reports: https://github.com/mvv/text-printer/issues
+
+Author: Mikhail Vorozhtsov <mikhail.vorozhtsov@gmail.com>
+Maintainer: Mikhail Vorozhtsov <mikhail.vorozhtsov@gmail.com>
+Copyright: 2013 Mikhail Vorozhtsov <mikhail.vorozhtsov@gmail.com>
+License: BSD3
+License-File: LICENSE
+
+Cabal-Version: >= 1.10.0
+Build-Type: Simple
+
+Source-Repository head
+  Type: git
+  Location: https://github.com/mvv/text-printer.git
+
+Library
+  Default-Language: Haskell2010
+  Build-Depends:
+    base >= 4 && < 5,
+    bytestring >= 0.10,
+    text,
+    pretty
+  Hs-Source-Dirs: src
+  GHC-Options: -Wall
+  Exposed-Modules:
+    Text.Printer
+
+Test-Suite tests
+  Default-Language: Haskell2010
+  Type: exitcode-stdio-1.0
+  Build-Depends:
+    base                       >= 4 && < 5,
+    test-framework             >= 0.5,
+    test-framework-quickcheck2 >= 0.2,
+    QuickCheck                 >= 2.4,
+    text-printer
+  Hs-Source-Dirs: tests
+  GHC-Options: -Wall
+  Main-Is: Tests.hs
+
