diff --git a/.gitignore b/.gitignore
new file mode 100644
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+# .gitignore for PercentFormat
+
+# General
+**.swp
+**~
+etc/
+
+# Haskell
+**.hi
+**.o
+**.dyn_hi
+**.dyn_o
+**.hpc
+**.hpc-*
+**.tix
+
+# Haskell Cabal
+**/.cabal-sandbox
+**/cabal.sandbox.config
+**/dist/
+
+# hasktags
+**/idx/
+**/TAGS
+**/tags
+
+# specific files and folders
+doc
+mk/toplibs
+bench/examples
+bench/speculate
+tests/test-main
+tests/test-number
+tests/test-prop
+tests/test-quotient
+tests/test-spec
+tests/test-unit
+tests/test-scientific
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,82 @@
+# .travis.yml file for percent-format
+#
+# Copyright:   (c) 2018 Rudy Matela
+# License:     3-Clause BSD  (see the file LICENSE)
+# Maintainer:  Rudy Matela <rudy@matela.com.br>
+
+language: c  # not really
+
+notifications:
+  email:
+    on_failure: change
+
+sudo: false
+
+cache:
+  directories:
+  - $HOME/.ghc
+  - $HOME/.cabal
+  - $HOME/.stack
+
+before_install:
+- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
+- ghc --version
+- cabal --version
+- rm -f ~/.cabal/config && cabal update
+# Download and unpack the stack executable
+# "Once Travis whitelists the stack.dev files," simply include stack in the
+# addons section. -- https://docs.haskellstack.org/en/stable/travis_ci/
+- mkdir -p ~/.local/bin
+- export PATH=$HOME/.local/bin:$PATH
+- travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
+- stack config set system-ghc --global true
+- stack --version
+- cabal install leancheck
+
+script:
+- make && make test && make haddock HADDOCKFLAGS=
+- make test-sdist
+- cabal test
+- stack --no-terminal --skip-ghc-check test
+
+matrix:
+  allow_failures:
+  - ghc: 'head'
+  include:
+  - ghc: 'head'
+    env:                   GHCVER=head         CABALVER=head
+    addons: {apt: {packages: [ghc-head,   cabal-install-head], sources: hvr-ghc}}
+  - ghc: '8.4'
+    env:                   GHCVER=8.4.2        CABALVER=2.2
+    addons: {apt: {packages: [ghc-8.4.2,  cabal-install-2.2],  sources: hvr-ghc}}
+  - ghc: '8.2'
+    env:                   GHCVER=8.2.2        CABALVER=2.0
+    addons: {apt: {packages: [ghc-8.2.2,  cabal-install-2.0],  sources: hvr-ghc}}
+  - ghc: '8.0'
+    env:                   GHCVER=8.0.2        CABALVER=1.24
+    addons: {apt: {packages: [ghc-8.0.2,  cabal-install-1.24], sources: hvr-ghc}}
+  - ghc: '7.10'
+    env:                   GHCVER=7.10.3       CABALVER=1.22
+    addons: {apt: {packages: [ghc-7.10.3, cabal-install-1.22], sources: hvr-ghc}}
+  # we only support stack with GHC >= 7.10
+  - ghc: '7.8'
+    env:                   GHCVER=7.8.4        CABALVER=1.18
+    addons: {apt: {packages: [ghc-7.8.4,  cabal-install-1.18], sources: hvr-ghc}}
+    before_install:
+    - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
+    - ghc --version
+    - cabal --version
+    - rm -f ~/.cabal/config && cabal update
+    - cabal install Cabal==1.18.*
+    - cabal install leancheck
+    script:
+    - make && make test && make haddock HADDOCKFLAGS=
+    - cabal test
+# Hugs is not a GHC version.  But this is how travis can handle it.
+  - ghc: 'hugs'
+    env:                   GHCVER=hugs
+    addons: {apt: {packages: [hugs]}}
+    before_install:
+    - echo ":version" | hugs
+    script:
+    - make hugs-test
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2016-2018, Rudy Matela
+
+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 name of Rudy Matela nor the names of other
+      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/Makefile b/Makefile
new file mode 100644
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,150 @@
+# Makefile for percentformat
+#
+# Copyright:   (c) 2015-2018 Rudy Matela
+# License:     3-Clause BSD  (see the file LICENSE)
+# Maintainer:  Rudy Matela <rudy@matela.com.br>
+TESTS = \
+  tests/test-spec \
+  tests/test-unit \
+  tests/test-number \
+  tests/test-scientific \
+  tests/test-quotient \
+  tests/test-prop
+EGS =
+BENCHS = bench/speculate
+GHCIMPORTDIRS = src:tests
+GHCFLAGS = -O2 $(shell grep -q "Arch Linux" /etc/lsb-release && echo -dynamic)
+HADDOCKFLAGS = --no-print-missing-docs
+HUGSIMPORTDIRS = .:./src:./tests:/usr/lib/hugs/packages/*
+
+all: mk/toplibs $(TESTS)
+
+test: $(patsubst %,%.test,$(TESTS)) diff-test
+
+test-via-cabal:
+	cabal clean  &&  cabal configure --enable-tests  && cabal test
+
+test-via-stack:
+	stack test
+
+test-sdist:
+	./tests/test-sdist
+
+legacy-test: # needs ghc-8.0 .. ghc-7.8 installed as such
+	make clean  &&  make test GHC=ghc-8.2  GHCFLAGS="-Werror -dynamic"
+	make clean  &&  make test GHC=ghc-8.0  GHCFLAGS="-Werror -dynamic"
+	make clean  &&  make test GHC=ghc-7.10 GHCFLAGS="-Werror -dynamic"
+	make clean  &&  make test GHC=ghc-7.8  GHCFLAGS="-Werror -dynamic"
+	make clean  &&  make test
+
+legacy-test-via-cabal: # needs similarly named cabal wrappers
+	cabal clean  &&  cabal-ghc-8.2  configure  &&  cabal-ghc-8.2  test
+	cabal clean  &&  cabal-ghc-8.0  configure  &&  cabal-ghc-8.0  test
+	cabal clean  &&  cabal-ghc-7.10 configure  &&  cabal-ghc-7.10 test
+	cabal clean  &&  cabal-ghc-7.8  configure  &&  cabal-ghc-7.8  test
+	cabal clean  &&  cabal test
+
+prepare:
+	cabal update  &&  cabal install leancheck
+
+prepare-legacy-test:
+	cabal-ghc-8.2  update  &&  cabal-ghc-8.2  install leancheck
+	cabal-ghc-8.0  update  &&  cabal-ghc-8.0  install leancheck
+	cabal-ghc-7.10 update  &&  cabal-ghc-7.10 install leancheck
+	cabal-ghc-7.8  update  &&  cabal-ghc-7.8  install leancheck
+
+hlint:
+	hlint \
+	  .
+
+doctest:
+	doctest -isrc src/Text/PercentFormat.hs
+
+bench: $(BENCHS)
+	./bench/speculate
+
+%.test: %
+	./$<
+
+clean: clean-hi-o
+	rm -f $(TESTS) $(EG) mk/toplibs
+	rm -rf doc/
+
+ghci: mk/All.ghci
+
+hugs: mk/All.hugs
+
+hugs-test: \
+  tests/test-spec.runhugs \
+  tests/test-unit.runhugs \
+  tests/test-number.runhugs \
+  tests/test-scientific.runhugs
+
+diff-test: examples-diff-test
+
+update-diff-test: update-examples-diff-test
+
+examples-diff-test: bench/examples
+	./bench/examples             | diff -rud tests/diff/examples.out         -
+	./bench/examples "%r"        | diff -rud tests/diff/examples-r.out       -
+	./bench/examples "%6r"       | diff -rud tests/diff/examples-6r.out      -
+	./bench/examples "%-6r"      | diff -rud tests/diff/examples--6r.out     -
+	./bench/examples "%s"        | diff -rud tests/diff/examples-s.out       -
+	./bench/examples "%6s"       | diff -rud tests/diff/examples-6s.out      -
+	./bench/examples "%-6s"      | diff -rud tests/diff/examples--6s.out     -
+	./bench/examples "%i"        | diff -rud tests/diff/examples-i.out       -
+	./bench/examples "%3i"       | diff -rud tests/diff/examples-3i.out      -
+	./bench/examples "%03i"      | diff -rud tests/diff/examples-03i.out     -
+	./bench/examples "%-3i"      | diff -rud tests/diff/examples--3i.out     -
+	./bench/examples "%+3i"      | diff -rud tests/diff/examples-+3i.out     -
+	./bench/examples "% 3i"      | diff -rud tests/diff/examples-_3i.out     -
+	./bench/examples "%f"        | diff -rud tests/diff/examples-f.out       -
+	./bench/examples "%5f"       | diff -rud tests/diff/examples-5f.out      -
+	./bench/examples "%+5f"      | diff -rud tests/diff/examples-+5f.out     -
+	./bench/examples "%-5f"      | diff -rud tests/diff/examples--5f.out     -
+	./bench/examples "%11.2f"    | diff -rud tests/diff/examples-11p2f.out   -
+	./bench/examples "%-11.2f"   | diff -rud tests/diff/examples--11p2f.out  -
+	./bench/examples "%c"        | diff -rud tests/diff/examples-c.out       -
+	./bench/examples "%2c"       | diff -rud tests/diff/examples-2c.out      -
+	./bench/examples "%b"        | diff -rud tests/diff/examples-b.out       -
+	./bench/examples "%o"        | diff -rud tests/diff/examples-o.out       -
+	./bench/examples "%x"        | diff -rud tests/diff/examples-x.out       -
+	./bench/examples "%X"        | diff -rud tests/diff/examples-X.out       -
+	./bench/examples "abc"       | diff -rud tests/diff/examples-abc.out     -
+
+update-examples-diff-test: bench/examples
+	./bench/examples             > tests/diff/examples.out
+	./bench/examples "%r"        > tests/diff/examples-r.out
+	./bench/examples "%6r"       > tests/diff/examples-6r.out
+	./bench/examples "%-6r"      > tests/diff/examples--6r.out
+	./bench/examples "%s"        > tests/diff/examples-s.out
+	./bench/examples "%6s"       > tests/diff/examples-6s.out
+	./bench/examples "%-6s"      > tests/diff/examples--6s.out
+	./bench/examples "%i"        > tests/diff/examples-i.out
+	./bench/examples "%3i"       > tests/diff/examples-3i.out
+	./bench/examples "%03i"      > tests/diff/examples-03i.out
+	./bench/examples "%-3i"      > tests/diff/examples--3i.out
+	./bench/examples "%+3i"      > tests/diff/examples-+3i.out
+	./bench/examples "% 3i"      > tests/diff/examples-_3i.out
+	./bench/examples "%f"        > tests/diff/examples-f.out
+	./bench/examples "%5f"       > tests/diff/examples-5f.out
+	./bench/examples "%+5f"      > tests/diff/examples-+5f.out
+	./bench/examples "%-5f"      > tests/diff/examples--5f.out
+	./bench/examples "%11.2f"    > tests/diff/examples-11p2f.out
+	./bench/examples "%-11.2f"   > tests/diff/examples--11p2f.out
+	./bench/examples "%c"        > tests/diff/examples-c.out
+	./bench/examples "%2c"       > tests/diff/examples-2c.out
+	./bench/examples "%b"        > tests/diff/examples-b.out
+	./bench/examples "%o"        > tests/diff/examples-o.out
+	./bench/examples "%x"        > tests/diff/examples-x.out
+	./bench/examples "%X"        > tests/diff/examples-X.out
+	./bench/examples "abc"       > tests/diff/examples-abc.out
+
+# NOTE: (very hacky!) the following target allows parallel compilation (-jN) of
+# eg and tests programs so long as they don't share dependencies _not_ stored
+# in src/ and tests/.  Runnable binaries should depend on mk/toplibs instead of
+# actual Haskell source files
+mk/toplibs: mk/Toplibs.o
+	touch mk/toplibs
+
+include mk/haskell.mk
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,49 @@
+PercentFormat -- C-like printf-style string formatting for Haskell
+==================================================================
+
+The `Text.PercentFormat` library provides printf-style string formatting.  It
+provides a `%` operator (as in Ruby or Python) and uses the old C-printf-style
+format you know and love.
+
+This library differs from `Text.Printf` in that it does not rely on custom
+typeclasses -- it works on anything that is a `Show` instance.
+
+Formatting one value:
+
+	> import Text.PercentFormat
+	> "Hello %s!" -% "World"
+	"Hello World!"
+
+Formatting three values, tuple style:
+
+	> "load average: %1.2f %1.2f %1.2f" -%%% (0.00, 0.066, 0.11)
+	"load average: 0.00 0.07 0.11"
+
+Formatting three values, chain style:
+
+	> "load average: %1.2f %1.2f %1.2f" % 0.00 % 0.066 -% 0.11
+	"load average: 0.00 0.07 0.11"
+
+
+To produce a string with a percent sign (`%`),
+use two percent signs (`%%`):
+
+	> "memory usage: %i%%" -% 13
+	"memory usage: 13%"
+
+
+Percent signs are duplicated when using the `%` operator to allow chaining:
+
+	> "percent sign: %s, memory usage: %i%%" % "%" % 87
+	"percent sign: %%, memory usage: 87%%"
+
+_Always_ use the `-%` operator when formatting the _last value_
+to remove duplicate `%` signs:
+
+	> "percent sign: %s, memory usage: %i%%" % "%" -% 87
+	"percent sign: %, memory usage: 87%"
+
+To print, just prefix you format expression with `putStrLn $`:
+
+	> putStrLn $ "Hello %s!" -% "World"
+	Hello World!
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/TODO.md b/TODO.md
new file mode 100644
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,31 @@
+TO DO list for PercentFormat
+============================
+
+TO DO list for PercentFormat, more or less in order:
+
+* when `%s` argument is char, generate `<char>` instead of `'<char>'`;
+
+* when `%i`/`%f`/etc argument is a number encoded in a string, parse the number
+  and make it work;
+
+* add `%e`;
+
+* release percent-format package on Hackage;
+
+* add link to Haddock documentation on README;
+
+* add `%q` (see `tests/test-number.hs`);
+
+* sweep `TODO`s scattered through files;
+
+* *maybe*: make "%l" for lists:
+
+    > "%l" % [1,2,3] -- regular
+    [1,2,3]
+    > "% l" % [1,2,3] -- add spaces
+    [1, 2, 3]
+    > "%\nl" % [1,2,3] -- add linebreaks
+    [ 1
+    , 2
+    , 3 ]
+
diff --git a/bench/examples.hs b/bench/examples.hs
new file mode 100644
--- /dev/null
+++ b/bench/examples.hs
@@ -0,0 +1,28 @@
+-- Copyright (c) 2018 Rudy Matela.
+-- Distributed under the 3-Clause BSD licence (see the file LICENSE).
+import Text.PercentFormat
+import Test.LeanCheck
+import System.Environment (getArgs)
+
+put :: String -> IO ()
+put s  =  putStrLn . unlines
+       $  (s %&) `map` take 12 (list :: [String])
+       ++ (s %&) `map` take 12 (list :: [Int])
+       ++ (s %&) `map` take 12 (list :: [Float])
+       ++ (s %&) `map` take 12 (list :: [Rational])
+       ++ (s %&) `map` take 12 (list :: [Char])
+       ++ (s %&) `map` take 12 (list :: [Bool])
+
+main :: IO ()
+main = do
+  as <- getArgs
+  let s = case as of
+          [] -> "%r"
+          (s:_) -> s
+  put s
+
+(%&) :: Show a => String -> a -> String
+s %& x = "%r -%% %12-s == %r" % s % show11 x -% (s -% x)
+
+show11 :: Show a => a -> String
+show11 x = showsPrec 11 x ""
diff --git a/bench/speculate.hs b/bench/speculate.hs
new file mode 100644
--- /dev/null
+++ b/bench/speculate.hs
@@ -0,0 +1,17 @@
+-- Copyright (c) 2018 Rudy Matela.
+-- Distributed under the 3-Clause BSD licence (see the file LICENSE).
+import Test.Speculate
+import Text.PercentFormat
+
+-- TODO: make this work by tweaking the String enumeration
+main :: IO ()
+main = speculate args
+  { maxTests = 100000
+  , constants =
+      [ constant "%"   ((%)   ->:> int)
+      , constant "-%"  ((-%)  ->:> int)
+--    , constant "%%"  ((%%)  ->:> (int,int))
+--    , constant "-%%" ((-%%) ->:> (int,int))
+      , constant "/%"  (/%)
+      ]
+  }
diff --git a/mk/All.hs b/mk/All.hs
new file mode 100644
--- /dev/null
+++ b/mk/All.hs
@@ -0,0 +1,12 @@
+module All
+  ( module Text.PercentFormat
+  , module Text.PercentFormat.Spec
+  , module Text.PercentFormat.Utils
+  , module Text.PercentFormat.Quotient
+  )
+where
+
+import Text.PercentFormat
+import Text.PercentFormat.Spec hiding (leftAlign)
+import Text.PercentFormat.Utils
+import Text.PercentFormat.Quotient hiding ((%))
diff --git a/mk/Toplibs.hs b/mk/Toplibs.hs
new file mode 100644
--- /dev/null
+++ b/mk/Toplibs.hs
@@ -0,0 +1,5 @@
+-- Using ghc --make in this module triggers compilation of every library.
+module Toplibs () where
+
+import Text.PercentFormat ()
+import Text.PercentFormat.Spec ()
diff --git a/mk/depend.mk b/mk/depend.mk
new file mode 100644
--- /dev/null
+++ b/mk/depend.mk
@@ -0,0 +1,104 @@
+bench/examples: \
+  bench/examples.hs \
+  mk/toplibs
+bench/examples.o: \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs \
+  bench/examples.hs
+bench/speculate: \
+  bench/speculate.hs \
+  mk/toplibs
+bench/speculate.o: \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs \
+  bench/speculate.hs
+mk/All.o: \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs \
+  mk/All.hs
+mk/Toplibs.o: \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs \
+  mk/Toplibs.hs
+src/Text/PercentFormat: \
+  mk/toplibs
+src/Text/PercentFormat.o: \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs
+src/Text/PercentFormat/Quotient.o: \
+  src/Text/PercentFormat/Quotient.hs
+src/Text/PercentFormat/Spec.o: \
+  src/Text/PercentFormat/Spec.hs
+src/Text/PercentFormat/Utils.o: \
+  src/Text/PercentFormat/Utils.hs
+tests/test-number.o: \
+  tests/Test.hs \
+  tests/test-number.hs \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs
+tests/test-number: \
+  tests/Test.hs \
+  tests/test-number.hs \
+  mk/toplibs
+tests/Test.o: \
+  tests/Test.hs \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs
+tests/test-prop.o: \
+  tests/test-prop.hs \
+  tests/Test.hs \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs
+tests/test-prop: \
+  tests/test-prop.hs \
+  tests/Test.hs \
+  mk/toplibs
+tests/test-quotient.o: \
+  tests/test-quotient.hs \
+  tests/Test.hs \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs
+tests/test-quotient: \
+  tests/test-quotient.hs \
+  tests/Test.hs \
+  mk/toplibs
+tests/test-spec.o: \
+  tests/test-spec.hs \
+  tests/Test.hs \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs
+tests/test-spec: \
+  tests/test-spec.hs \
+  tests/Test.hs \
+  mk/toplibs
+tests/test-unit.o: \
+  tests/test-unit.hs \
+  tests/Test.hs \
+  src/Text/PercentFormat/Utils.hs \
+  src/Text/PercentFormat/Spec.hs \
+  src/Text/PercentFormat/Quotient.hs \
+  src/Text/PercentFormat.hs
+tests/test-unit: \
+  tests/test-unit.hs \
+  tests/Test.hs \
+  mk/toplibs
diff --git a/mk/ghcdeps b/mk/ghcdeps
new file mode 100644
--- /dev/null
+++ b/mk/ghcdeps
@@ -0,0 +1,49 @@
+#!/bin/bash
+#
+# ghcdeps: generate Haskell make dependencies for compiling with GHC.
+#
+# Copyright (c) 2015-2017 Rudy Matela.
+# Distributed under the 3-Clause BSD licence.
+#
+# From a list of files provided on standard input,
+# generate flat make dependencies.
+#
+# Transitive relations are repeated.
+#
+# Usage:
+#   $ ghcdeps -isomedir:someother <<LIST
+#   program.hs
+#   Library.hs
+#   Path/To/Library.hs
+#   LIST
+#   program: ...
+#   Library.o: ...
+#   Path/To/Library.o: ...
+#
+# Or:
+#   $ find -name \*.hs -o -name \*.lhs | ghcdeps [GHCFLAGS]
+#   ...
+#
+# Note that when using find, you have to remember to exclude unecessary files,
+# e.g.:  dist, Setup.hs
+#
+# Yes, this is hacky.  But hey, it works.
+while read fn
+do
+	ghc "$@" -dep-suffix=. -dep-makefile=tmp.mk -M "$fn" ||
+	exit 1
+	deps="`cat tmp.mk |
+	       grep "hs$" |
+	       sort |
+	       sed -e "s/.*: //" |
+	       tac |
+	       tr '\n' ' ' |
+	       sed -e "s/ $//"`"
+	obj=`echo $fn | sed -e 's,^\./,,;s/\.hs$/.o/'`
+	bin=`echo $fn | sed -e 's,^\./,,;s/\.hs$//'`
+	echo "$obj: $deps"
+	grep -q "main" "$fn" && echo "$bin: `echo $deps | sed -e 's|[^ ]*src[^ ]*||g'` mk/toplibs"
+	rm -f tmp.mk
+done |
+sort |
+sed -e 's,  *, \\\n  ,g'
diff --git a/mk/haddock-i b/mk/haddock-i
new file mode 100644
--- /dev/null
+++ b/mk/haddock-i
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# haddock-i: list haddock's -i parameters.
+#
+# Copyright (c) 2015-2017 Rudy Matela.
+# Distributed under the 3-Clause BSD licence.
+#
+# $ haddock-i <package1> <package2> ... <packageN>
+#
+# will print -i parameters necessary for haddock to link to Haddock
+# documentation installed on your system, so you can:
+#
+# $ haddock-i base template-haskell | xargs haddock <files>
+errxit() {
+	echo "$@" > /dev/stderr
+	exit 1
+}
+
+for pkg in "$@"; do
+	ghc-pkg field $pkg haddock-html,haddock-interfaces ||
+	errxit "error: haddock-i: cannot find package $pkg (ghc-pkg)"
+done |
+sed "s/.*: //" |
+sed "N;s/\n/,/;s/^/-i/"
diff --git a/mk/haskell.mk b/mk/haskell.mk
new file mode 100644
--- /dev/null
+++ b/mk/haskell.mk
@@ -0,0 +1,110 @@
+# Implicit rules for compiling Haskell code.
+#
+# Copyright (c) 2015-2018 Rudy Matela.
+# Distributed under the 3-Clause BSD licence.
+#
+# You can optionally configure the "Configuration variables" below in your main
+# makefile, e.g.:
+#
+#   GHCIMPORTDIRS = path/to/dir:path/to/another/dir
+#   GHCFLAGS = -O2 -dynamic
+#   GHC = ghc-7.6
+#   include haskell.mk
+
+
+
+# Configuration variables
+
+# GHC Parameters
+GHCIMPORTDIRS ?=
+GHCFLAGS ?=
+GHC ?= ghc
+GHCCMD = $(GHC) -i$(GHCIMPORTDIRS) $(GHCFLAGS)
+
+# Hugs Parameters
+HUGSIMPORTDIRS ?= "/usr/lib/hugs/packages/*"
+HUGSFLAGS ?=
+CPPHS_HUGS ?= cpphs-hugs --noline -D__HUGS__
+HUGS ?= hugs
+RUNHUGS ?= runhugs
+HUGSCMD    = $(HUGS)    -F"$(CPPHS_HUGS)" -P$(HUGSIMPORTDIRS) $(HUGSFLAGS)
+RUNHUGSCMD = $(RUNHUGS) -F"$(CPPHS_HUGS)" -P$(HUGSIMPORTDIRS) $(HUGSFLAGS)
+
+
+# Makefile where to keep the dependencies
+DEPMK ?= mk/depend.mk
+
+# LIB_HSS: all library Haskell files
+# ALL_HSS: all Haskell files
+# You can override ALL/LIB_HSS in your main Makefile
+LIST_LIB_HSS ?= find src -name "*.hs"
+LIST_ALL_HSS ?= find \( -path "./dist" -o -path "./.stack-work" \) -prune \
+                     -o -name "*.*hs" -print
+LIB_HSS ?= $(shell $(LIST_LIB_HSS))
+ALL_HSS ?= $(shell $(LIST_ALL_HSS))
+
+PKGNAME = $(shell cat *.cabal | grep "^name:"    | sed -e "s/name: *//")
+
+
+# Implicit rules
+%.hi %.o: %.hs
+	$(GHCCMD) $< && touch $@
+
+%: %.hs
+	$(GHCCMD) $< && touch $@
+
+.PHONY: %.ghci
+%.ghci: %.hs
+	$(GHCCMD) -O0 --interactive $<
+
+.PHONY: %.hugs
+%.hugs: %.hs
+	$(HUGSCMD) $<
+
+.PHONY: %.runhugs
+%.runhugs: %.hs
+	$(RUNHUGSCMD) $<
+
+
+# Cleaning rule (add as a clean dependency)
+.PHONY: clean-hi-o
+clean-hi-o:
+	find $(ALL_HSS) | sed -e 's/hs$$/o/'      | xargs rm -f
+	find $(ALL_HSS) | sed -e 's/hs$$/hi/'     | xargs rm -f
+	find $(ALL_HSS) | sed -e 's/hs$$/dyn_o/'  | xargs rm -f
+	find $(ALL_HSS) | sed -e 's/hs$$/dyn_hi/' | xargs rm -f
+
+
+# Update dependency file
+.PHONY: depend
+depend:
+	find $(ALL_HSS) | ./mk/ghcdeps -i$(GHCIMPORTDIRS) $(GHCFLAGS) > $(DEPMK)
+
+# haddock rules
+haddock: doc/index.html
+
+clean-haddock:
+	rm -f doc/*.{html,css,js,png,gif,json} README.html
+
+upload-haddock:
+	@echo "use \`cabal upload -d' instead"
+	@echo "(but 1st: cabal install --only-dependencies --enable-documentation)"
+	@echo "(to just compile docs: cabal haddock --for-hackage)"
+	@echo "(on Arch Linux, use: cabal haddock --for-hackage --haddock-options=--optghc=-dynamic)"
+
+doc/index.html: $(LIB_HSS)
+	./mk/haddock-i base template-haskell | xargs \
+	haddock --html -odoc $(LIB_HSS) $(HADDOCKFLAGS) --title=$(PKGNAME)
+
+# lists all Haskell source files
+list-all-hss:
+	@find $(ALL_HSS)
+
+# lists library Haskell source files
+list-lib-hss:
+	@find $(LIB_HSS)
+
+show-pkgname:
+	@echo $(PKGNAME)
+
+include $(DEPMK)
diff --git a/percent-format.cabal b/percent-format.cabal
new file mode 100644
--- /dev/null
+++ b/percent-format.cabal
@@ -0,0 +1,103 @@
+-- Cabal file for PercentFormat
+
+name:                percent-format
+version:             0.0.1
+synopsis:            simple printf-style string formatting
+description:
+  The @Text.PercentFormat@ library provides printf-style string formatting.  It
+  provides a @%@ operator (as in Ruby or Python) and uses the old
+  C-printf-style format you know and love.
+
+homepage:            https://github.com/rudymatela/percent-format#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Rudy Matela <rudy@matela.com.br>
+maintainer:          Rudy Matela <rudy@matela.com.br>
+category:            Testing
+build-type:          Simple
+cabal-version:       1.18
+
+extra-doc-files: README.md
+               , TODO.md
+extra-source-files: .gitignore
+                  , Makefile
+                  , mk/depend.mk
+                  , mk/ghcdeps
+                  , mk/haddock-i
+                  , mk/All.hs
+                  , mk/haskell.mk
+                  , mk/Toplibs.hs
+                  , stack.yaml
+                  , tests/test-sdist
+                  , .travis.yml
+                  , bench/examples.hs
+                  , bench/speculate.hs
+                  , tests/diff/*.out
+tested-with: GHC==8.4, GHC==8.2, GHC==8.0, GHC==7.10, GHC==7.8
+
+
+source-repository head
+  type:            git
+  location:        https://github.com/rudymatela/percent-format
+
+source-repository this
+  type:            git
+  location:        https://github.com/rudymatela/percent-format
+  tag:             v0.0.1
+
+library
+  exposed-modules: Text.PercentFormat
+                 , Text.PercentFormat.Quotient
+                 , Text.PercentFormat.Spec
+                 , Text.PercentFormat.Utils
+  hs-source-dirs:      src
+  build-depends:       base >= 4 && < 5
+  default-language:    Haskell2010
+
+test-suite number
+  type:                exitcode-stdio-1.0
+  main-is:             test-number.hs
+  other-modules:       Test
+  hs-source-dirs:      tests
+  build-depends:       base >= 4 && < 5, percent-format, leancheck
+  default-language:    Haskell2010
+
+test-suite scientific
+  type:                exitcode-stdio-1.0
+  main-is:             test-scientific.hs
+  other-modules:       Test
+  hs-source-dirs:      tests
+  build-depends:       base >= 4 && < 5, percent-format, leancheck
+  default-language:    Haskell2010
+
+test-suite prop
+  type:                exitcode-stdio-1.0
+  main-is:             test-prop.hs
+  other-modules:       Test
+  hs-source-dirs:      tests
+  build-depends:       base >= 4 && < 5, percent-format, leancheck
+  default-language:    Haskell2010
+
+test-suite quotient
+  type:                exitcode-stdio-1.0
+  main-is:             test-quotient.hs
+  other-modules:       Test
+  hs-source-dirs:      tests
+  build-depends:       base >= 4 && < 5, percent-format, leancheck
+  default-language:    Haskell2010
+
+test-suite unit
+  type:                exitcode-stdio-1.0
+  main-is:             test-unit.hs
+  other-modules:       Test
+  hs-source-dirs:      tests
+  build-depends:       base >= 4 && < 5, percent-format, leancheck
+  default-language:    Haskell2010
+
+test-suite spec
+  type:                exitcode-stdio-1.0
+  main-is:             test-spec.hs
+  other-modules:       Test
+  hs-source-dirs:      tests
+  build-depends:       base >= 4 && < 5, percent-format, leancheck
+  default-language:    Haskell2010
diff --git a/src/Text/PercentFormat.hs b/src/Text/PercentFormat.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/PercentFormat.hs
@@ -0,0 +1,517 @@
+-- |
+-- Module      : Text.PercentFormat
+-- Copyright   : (c) 2016-2018 Rudy Matela
+-- License     : 3-Clause BSD  (see the file LICENSE)
+-- Maintainer  : Rudy Matela <rudy@matela.com.br>
+--
+-- The "Text.PercentFormat" library provides printf-style string formatting.
+-- It provides a '%' operator (as in Ruby or Python)
+-- and uses the old C-printf-style format you know and love.
+--
+-- This library differs from "Text.Printf" in that it does not rely on custom
+-- typeclasses -- it works on anything that is a 'Show' instance that produces
+-- output in the supported formats.
+--
+--
+-- Formatting one value with '-%':
+--
+-- > > "Hello %s!" -% "World"
+-- > "Hello World!"
+--
+--
+-- Formatting three values, tuple style, with '-%%%':
+--
+-- > > "load average: %1.2f %1.2f %1.2f" -%%% (0.00, 0.066, 0.11)
+-- > "load average: 0.00 0.07 0.11"
+--
+--
+-- Formatting three values, chain style, with '%' and '-%':
+--
+-- > > "load average: %1.2f %1.2f %1.2f" % 0.00 % 0.066 -% 0.11
+-- > "load average: 0.00 0.07 0.11"
+--
+--
+-- To produce a string with a percent sign (@%@),
+-- use two percent signs (@%%@):
+--
+-- > > "memory usage: %i%%" -% 13
+-- > "memory usage: 13%"
+--
+--
+-- Percent signs are duplicated when using the '%' operator to allow chaining
+-- (further formats):
+--
+-- > > "percent sign: %s, memory usage: %i%%" % "%" % 87
+-- > "percent sign: %%, memory usage: 87%%"
+--
+-- /Always/ use the '-%' operator when formatting the /last value/
+-- to remove duplicate @%@ signs:
+--
+-- > > "percent sign: %s, memory usage: %i%%" % "%" -% 87
+-- > "percent sign: %, memory usage: 87%"
+--
+-- To print, just prefix you format expression with "@putStrLn $@":
+--
+-- > > putStrLn $ "Hello %s!" -% "World"
+-- > Hello World!
+--
+--
+-- == Supported formats
+--
+-- * /r/ -- 'show' representation as-is (including quotes for strings).
+--
+--     > > "%r" % "string"
+--     > "\"string\""
+--
+--     > > "%r" % Just 10
+--     > "Just 10"
+--
+-- * /s/ -- string.  If the argument is 'show'ed as a 'String', intersperse it,
+--          otherwise include representation in whole.
+--
+--     > > "%s" % "string"
+--     > "string"
+--
+--     > > "%s" % 10
+--     > "10"
+--
+--     > > "%s" % Just "string"
+--     > "Just \"string\""
+--
+-- * /c/ -- Argument is converted to a single character.
+--   Accepts arguments that when 'show'ed are represented as 'Char's.
+--
+--     > > "%c" % 'a'
+--     > "a"
+--
+-- * /i/ -- Argument is converted to the nearest decimal integer.
+--   Accepts arguments that when 'show'ed are represented as either
+--   'Integer's, 'Rational's or 'Double's.
+--
+--     > > "%i" % 5040
+--     > 5040
+--
+--     > > "%i" % 3.141
+--     > 3
+--
+-- * /d/ -- Argument is converted to a decimal integer.
+--   Accepts arguments that when 'show'ed are represented as either
+--   'Integer's, 'Rational's or 'Double's.
+--
+--     > > "%d" % 5040
+--     > 5040
+--
+--     > > "%i" % 3.141
+--     > 3.141
+--
+-- * /x/ -- Argument is converted to hexadecimal format with lowercase letters.
+--   Accepts arguments that when 'show'ed are represented as either
+--   'Integer's, 'Rational's or 'Double's.
+--
+--     > > "%x" % 5040
+--     > "13b0"
+--
+--     Differently from C's @printf@, negative integers are printed prefixed with
+--     a minus (@-@) sign:
+--
+--     > > "%x" % (-5040)
+--     > "-13b0"
+--
+--     Differently from C's @printf@, this library is able to show hexadecimal
+--     fractional parts:
+--
+--     > > "%.6x" % pi
+--     > "3.243f6b"
+--
+-- * /X/ -- Argument is converted to hexadecimal format with capital letters.
+--   Accepts arguments that when 'show'ed are represented as either
+--   'Integer's, 'Rational's or 'Double's.
+--
+--     > > "%X" % 5040
+--     > "13B0"
+--
+-- * /o/ -- Argument is converted to octal format.
+--   Accepts arguments that when 'show'ed are represented as either
+--   'Integer's, 'Rational's or 'Double's.
+--
+--     > > "%o" % 5040
+--     > "11660"
+--
+--     > > "%.6o" % pi
+--     > "3.110376"
+--
+-- * /b/ -- Argument is converted to binary format.
+--   Accepts arguments that when 'show'ed are represented as either
+--   'Integer's, 'Rational's or 'Double's.
+--
+--     > > "%b" % 5040
+--     > "1001110110000"
+--
+--     > > "%.6b" % pi
+--     > "11.001001"
+--
+-- * /f/ -- Argument is converted to decimal format with a fractional part
+--   (even when the given argument is an integer).
+--   Accepts arguments that when 'show'ed are represented as either
+--   'Integer's, 'Rational's or 'Double's.
+--
+--     > > "%f" % 5040
+--     > "5040.0"
+--
+--     > > "%f" % pi
+--     > "3.141592653589793"
+--
+-- * /e/ -- Argument is converted to scientific notation.
+--       __This does not work yet.  To be added in a future version.__
+--
+-- * /q/ -- Argument is converted to a rational number.
+--       __This does not work yet.  To be added in a future version.__
+--
+--
+-- == Supported flag charaters
+--
+-- * /0/ -- the numeric value should be padded by zeros.
+--
+--     > > "%08i" % 5040
+--     > "00005040"
+--
+-- * /-/ -- left adjusted values.
+--
+--     > > "%-8i" % 5040
+--     > "5040    "
+--
+-- * / / -- leave a blank before a positive number.
+--
+--     > > "% i" % 5040
+--     > " 5040"
+--
+--     > > "% i" % (-5040)
+--     > "-5040"
+--
+-- * /+/ -- leave a plus sign before a positive number.
+--
+--     > > "%+i" % 5040
+--     > "+5040"
+--
+--     > > "%+i" % (-5040)
+--     > "-5040"
+--
+-- * /[1-9][0-9]*/ -- minimum field width.
+--
+--     > > "%8i" % 5040
+--     > "    5040"
+--
+-- * /.[0-9][0-9]*/ -- precision.
+--
+--     > > "%.2i" % 5040
+--     > "5040.00"
+--
+--     > > "%9.2i" % 5040
+--     > "  5040.00"
+--
+--
+-- == How does it work?
+--
+-- "Text.PercentFormat" works on values that are 'Show' instances producing
+-- results in the expected format.  Take for example the following number type:
+--
+-- > data Digit = Zero | One | Two | Three
+-- > instance Show Digit where
+-- >   show Zero   =  "0"
+-- >   show One    =  "1"
+-- >   show Two    =  "2"
+-- >   show Three  =  "3"
+--
+-- "Text.PercentFormat" works fine on it:
+--
+-- > > "%d %i %f %.2f" Zero One Two Three
+-- > "0 1 2 3.00"
+--
+-- Because when 'show'ed, values of this @Digit@ type are represented as 'Integer's.
+--
+--
+-- == Error Handling
+--
+-- This library is designed to avoid raising errors.
+-- If conversion cannot be performed an exclamation mark (@!@) is produced.
+-- If there are missing format strings an interrogation mark (@?@) is produced.
+-- For example:
+--
+-- > > "%d %d" -% "Ten"
+-- > "! ?"
+--
+-- The only two instances where errors are raised are:
+--
+-- 1. the argument values contain errors themselves:
+--
+--     > > "Hello %s!" % error "err"
+--     > *** Exception err
+--
+--     > > error "err" % "World"
+--     > *** Exception err
+--
+-- 2. the format string is not supported:
+--
+--     > > "%j" % 10
+--     > *** Exception: unknown format string `j'
+--
+--
+-- == Known bugs
+--
+-- * @"%x" % 3.1415926@ takes too long to run.
+--
+-- * @"%x" % pi@ takes /very very long/ to run.
+module Text.PercentFormat
+  ( (%)
+  , (-%)
+  , (/%)
+
+  , (%%)
+  , (%%%)
+  , (%%%%)
+  , (%%%%%)
+  , (%%%%%%)
+
+  , (-%%)
+  , (-%%%)
+  , (-%%%%)
+  , (-%%%%%)
+  , (-%%%%%%)
+
+  , (+%)
+  )
+where
+
+import Data.Maybe (listToMaybe, fromMaybe)
+import Data.Char (isDigit, toUpper)
+import Text.PercentFormat.Spec  as S
+import Text.PercentFormat.Utils hiding (align)
+import Text.PercentFormat.Quotient (maybeReadQ, digits, Quotient, infinity, nan)
+import qualified Text.PercentFormat.Quotient as Q
+import qualified Text.PercentFormat.Utils as U
+import Prelude hiding (showString, showChar)
+
+-- | Formats a single value into a string without finalizing:
+--   leaving duplicate percent signs & remaining format sequences.
+--
+-- > > "Hello %s!" % "World"
+-- > "Hello World!"
+--
+-- > > "processor usage: %d%%" % 67
+-- > "processor usage: 67%%"
+--
+-- > > "load avg: %.2f %.2f %.2f" % 0.666
+-- > "load avg: %0.67 %.2f %.2f"
+--
+-- Please use '-%' when formatting the last value into a string so that
+-- duplicate percent signs are removed.
+(%) :: Show a => String -> a -> String
+('%':s) % x =
+  case ty sp of
+  Percent     -> '%':'%': s' % x
+  ReprSpec    -> (duplicatePercents $ showRepr    sp x) ++ s'
+  StringSpec  -> (duplicatePercents $ showString  sp x) ++ s'
+  CharSpec    -> (duplicatePercents $ showChar    sp x) ++ s'
+  NumberSpec  -> (duplicatePercents $ showDigits  sp x) ++ s'
+  where
+  (sp,s') = parseSpec s
+(c:s)       % x = c : s % x
+""          % x = ""
+infixl 9 %
+
+showRepr :: Show a => Spec -> a -> String
+showRepr spec = align spec . show
+
+showString :: Show a => Spec -> a -> String
+showString spec s =
+  case maybeRead (show s) of
+  Nothing -> align spec (show s)
+  Just s  -> align spec s
+
+showChar :: Show a => Spec -> a -> String
+showChar spec c =
+  case maybeRead (show c) of
+  Nothing -> err '!' spec
+  Just c  -> align spec (c:"")
+
+-- TODO: refactor showDigits (currently very hacky)
+showDigits :: Show a => Spec -> a -> String
+showDigits spec x =
+  case maybeReadQ (show x) of
+  Nothing -> err '!' spec
+  Just q -> applyWhen (padWith spec /= ' ') (signal q ++)
+          . align' q
+          . applyWhen (padWith spec == ' ') (signal q ++)
+          . either id (\(ids,fds,pds) -> capitalize $ showds ids fds pds (precision spec))
+          . digits (base spec)
+          . round' (base spec) (precision spec)
+          $ q
+  where
+  capitalize = applyWhen (capitalizeDigits spec) (map toUpper)
+  signal q | q >= 0 = positivePrefix spec
+           | q <  0 = "-"
+  align' :: Quotient -> String -> String
+  align' q = if padWith spec == ' '
+               then align spec
+               else align spec{width = width spec - length (signal q)}
+  round' :: Int -> Maybe Int -> Quotient -> Quotient
+  round' _ _ q | Q.isInfinite q = q
+  round' _ _ q | Q.isNaN q      = q
+  round' _ Nothing  q = q
+  round' b (Just p) q = round (q * fromIntegral b ^ p) Q.% fromIntegral b ^ p
+  showds :: [Int] -> [Int] -> [Int] -> Maybe Int -> String
+  showds ids fds []  Nothing  | length fds < minPrecision spec
+                            = showds ids (fds ++ replicate (minPrecision spec - length fds) 0) [] Nothing
+  showds ids []  _   Nothing  = intsToDigits ids
+  showds ids fds pds (Just 0) = intsToDigits ids
+  showds ids fds pds Nothing  = intsToDigits ids ++ "."
+                             ++ intsToDigits fds ++ showPeriod pds
+  showds ids fds pds (Just pr) = intsToDigits ids ++ "."
+                              ++ intsToDigits (take pr (fds ++ loop pds ++ repeat 0))
+  showPeriod [] = ""
+  showPeriod xs = intsToDigits xs
+               ++ intsToDigits xs
+               ++ intsToDigits xs
+               ++ "..."
+
+err :: Char -> Spec -> String
+err c spec = align spec{padWith=c} (c:"")
+
+-- | Formats the last value into a string.
+--   This finalizes formatting, removing duplicate percent signs and replacing
+--   remaining format sequences with interrogation marks.
+--
+-- > > "Hello %s!" -% "World"
+-- > "Hello World!"
+--
+-- > > "processor usage: %d%%" -% 67
+-- > "processor usage: 67%"
+--
+-- > > "load avg: %.2f %.2f %.2f" % 0.666
+-- > "load avg: %0.67 ? ?"
+--
+-- Please use '%' if you intend to further format values (chaining).
+(-%) :: Show a => String -> a -> String
+s -% x = s % x /% '?'
+infixl 9 -%
+
+-- | Replaces "%%" by "%".  Any remaining occurrences of format strings are
+--   replaced by the given error character.  Field width is respected when
+--   possible.
+--
+-- > > "100%% %i" /% '?'
+-- > "100% ?"
+--
+-- > > "100%% %03i" /% '?'
+-- > "100% ???"
+(/%) :: String -> Char -> String
+s /% errChar = depercent s
+  where
+  depercent ('%':s) = let (spec,s') = parseSpec s
+                          s'' = case ty spec of
+                                Percent     -> "%"
+                                _           -> err errChar spec
+                      in s'' ++ depercent s'
+  depercent (c:s) = c : depercent s
+  depercent "" = ""
+
+-- | Aligns a string following a given spec.
+--
+-- > align spec{width=1} "asdf"
+-- "asdf"
+-- > align spec{width=5} "asdf"
+-- " asdf"
+-- > align spec{width=5, leftAlign=True} "asdf"
+-- "asdf "
+align :: Spec -> String -> String
+align spec = U.align (S.leftAlign spec) (padWith spec) (width spec)
+
+duplicatePercents :: String -> String
+duplicatePercents ('%':s) = '%':'%':duplicatePercents s
+duplicatePercents (c:s)   = c:duplicatePercents s
+duplicatePercents ""      = ""
+
+-- | Formats two values into a string without finalizing:
+--   leaving duplicate percent signs & remaining format sequences.
+--
+-- > > "%s %s!" %% ("Hello","World")
+-- > "Hello World!"
+--
+-- > > "load avg: %.2f %.2f %.2f" %% (0.666,0.333)
+-- > "load avg: %0.67 %0.33 %.2f"
+--
+-- In general:
+--
+-- > s %% (x,y) == s % x % y
+--
+-- Please use '-%%' if you don't intend to format values into a string any further.
+(%%) :: (Show a, Show b) => String -> (a,b) -> String
+s %% (x,y) = s % x % y
+
+-- | Formats three values into a string without finalizing.
+--
+-- > > "load avg: %.2f %.2f %.2f" %%% (0.666,0.333,0.1)
+-- > "load avg: %0.67 %0.33 %0.10"
+(%%%) :: (Show a, Show b, Show c) => String -> (a,b,c) -> String
+s %%% (x,y,z) = s % x % y % z
+
+-- | Formats four values into a string without finalizing.
+(%%%%) :: (Show a, Show b, Show c, Show d) => String -> (a,b,c,d) -> String
+s %%%% (x,y,z,w) = s % x % y % z % w
+
+-- | Formats five values into a string without finalizing.
+(%%%%%) :: (Show a, Show b, Show c, Show d, Show e)
+        => String -> (a,b,c,d,e) -> String
+s %%%%% (x,y,z,w,v) = s % x % y % z % w % v
+
+-- | Formats six values into a string without finalizing.
+(%%%%%%) :: (Show a, Show b, Show c, Show d, Show e, Show f)
+         => String -> (a,b,c,d,e,f) -> String
+s %%%%%% (x,y,z,w,v,u) = s % x % y % z % w % v % u
+
+-- | Formats two values into a string and finalizes it:
+--   removing duplicate percent signs & replacing remaining format sequences
+--   with interrogation marks.
+--
+-- > > "%s %s!" -%% ("Hello","World")
+-- > "Hello World!"
+--
+-- > > "load avg: %.2f %.2f %.2f" -%% (0.666,0.333)
+-- > "load avg: %0.67 %0.33 ?"
+--
+-- In general:
+--
+-- > s -%% (x,y) == s % x -% y
+--
+-- Please use '%%' if you intend to further format values.
+(-%%) :: (Show a, Show b) => String -> (a,b) -> String
+s -%% t = s %% t /% '?'
+
+-- | Formats three values into a string and finalizes it.
+--
+-- > > "load avg: %.2f %.2f %.2f" -%%% (0.666,0.333,0.1)
+-- > "load avg: %0.67 %0.33 %0.10"
+(-%%%) :: (Show a, Show b, Show c) => String -> (a,b,c) -> String
+s -%%% t = s %%% t /% '?'
+
+-- | Formats four values into a string and finalizes it.
+(-%%%%) :: (Show a, Show b, Show c, Show d) => String -> (a,b,c,d) -> String
+s -%%%% t = s %%%% t /% '?'
+
+-- | Formats five values into a string and finalizes it.
+(-%%%%%) :: (Show a, Show b, Show c, Show d, Show e)
+        => String -> (a,b,c,d,e) -> String
+s -%%%%% t = s %%%%% t /% '?'
+
+-- | Formats six values into a stirng and finalizes it.
+(-%%%%%%) :: (Show a, Show b, Show c, Show d, Show e, Show f)
+         => String -> (a,b,c,d,e,f) -> String
+s -%%%%%% t = s %%%%%% t /% '?'
+
+-- | Just an alias to '%' for use whenever "Data.Ratio" is in scope.
+--
+-- > import Data.Ratio
+-- > import Text.PercentFormat hiding ((%))
+-- > "..." +% 1 -% 2
+(+%) :: Show a => String -> a -> String
+(+%) = (%)
diff --git a/src/Text/PercentFormat/Quotient.hs b/src/Text/PercentFormat/Quotient.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/PercentFormat/Quotient.hs
@@ -0,0 +1,202 @@
+-- |
+-- Module      : Text.PercentFormat.Quotient
+-- Copyright   : (c) 2018 Rudy Matela
+-- License     : 3-Clause BSD  (see the file LICENSE)
+-- Maintainer  : Rudy Matela <rudy@matela.com.br>
+--
+-- The 'Quotient' datatype.  Similar to 'Rational' but allows @Infinity@ and
+-- @NaN@.
+module Text.PercentFormat.Quotient
+  ( Quotient
+  , (%)
+  , infinity
+  , nan
+  , isInfinite
+  , isNaN
+  , readQ
+  , maybeReadQ
+  , digits
+  , fracDigits
+  )
+where
+
+import Prelude hiding (isInfinite, isNaN)
+import Data.Char (isDigit)
+import Data.Maybe (fromMaybe)
+import Data.List (findIndex)
+import qualified Data.Ratio as R
+import Text.PercentFormat.Utils
+
+-- | Our own Ratio type that allows Infinity and NaN
+data Quotient = Integer :% Integer
+infixl 7 :%
+
+-- | 'Eq' instance for 'Quotient'.  Follows the identity property except for
+--   NaN which is different from itself (this is consistent with 'Float' &
+--   'Double' behaviour).
+instance Eq Quotient where
+  (0 :% 0) == _  =  False
+  _ == (0 :% 0)  =  False
+  (x :% y) == (x' :% y')  =  (x * y') == (x' * y)
+
+-- | 'Ord' instance for 'Quotient'.  Follows the regular order properties
+--   except for NaN.  When NaN is present in any of the operands of 'compare',
+--   'GT' is returned (consistent with 'Float' & 'Double').
+instance Ord Quotient where
+  (0 :% 0) `compare` _  =  GT
+  _ `compare` (0 :% 0)  =  GT
+  (x :% y) `compare` (x' :% y') = (x * y') `compare` (x' * y)
+
+instance Show Quotient where
+  showsPrec d (0 :% 0) = showString "NaN"
+  showsPrec d (x :% 0) | x < 0     = showParen (d > 6) $ showString "-Infinity"
+                       | otherwise =                     showString  "Infinity"
+  showsPrec d (x :% y) = showParen (d > 7)
+                       $ showsPrec 7 x . showString " % " . showsPrec 7 y
+
+-- | Smart-constructor for Quotients
+(%) :: Integer -> Integer -> Quotient
+0 % 0  =  0 :% 0         -- NaN
+x % 0  =  signum x :% 0  -- (+/-) Infinity
+x % y  =  (x * signum y `quot` d) :% (abs y `quot` d)
+  where
+  d = gcd x y
+infixl 7 %
+
+-- | Infinity.
+infinity :: Quotient
+infinity = 1 % 0
+
+-- | Not a number @(0 / 0)@.
+nan :: Quotient
+nan = 0 % 0
+
+-- | Returns whether a given quotient is an infinity (+/-).
+isInfinite :: Quotient -> Bool
+isInfinite q = q == infinity || q == (-infinity)
+
+-- | Returns if the quotient is not a number.
+isNaN :: Quotient -> Bool
+isNaN q = q /= q
+
+
+instance Num Quotient where
+  negate (x :% y)  =  negate x % y
+  (x :% y) + (x' :% y')  =  (x * y'  +  x' * y) % (y * y')
+  (x :% y) * (x' :% y')  =  (x * x') % (y * y')
+  abs (x :% y)  =  abs x % abs y
+  signum (x :% y)  =  signum x * signum y  %  1
+  fromInteger  =  (% 1)
+
+instance Fractional Quotient where
+  recip (x :% y) = y % x
+  fromRational q = R.numerator q % R.denominator q
+
+instance Real Quotient where
+  toRational (x :% y) = x R.% y
+
+instance RealFrac Quotient where
+  properFraction (x :% y) = (fromInteger q, r % y)
+    where (q,r) = quotRem x y
+
+-- TODO: change this ugly ad-hoc implementation into something that uses
+--       readsPrec and related functions
+maybeReadQ :: String -> Maybe Quotient
+maybeReadQ "Infinity" = Just infinity
+maybeReadQ "NaN"      = Just nan
+maybeReadQ "inf"      = Just infinity  -- for Hugs
+maybeReadQ "nan"      = Just nan       -- for Hugs
+maybeReadQ ('-':s)    = negate <$> maybeReadQ s
+  where
+  f <$> Nothing   =  Nothing
+  f <$> (Just x)  =  Just (f x)
+maybeReadQ ('(':s)    = case span (/= ')') s of
+                        (s',')':s'') -> maybeReadQ (s' ++ s'') -- ugly!
+                        _ -> Nothing
+maybeReadQ (d:s) | not (isDigit d) = Nothing
+maybeReadQ etc = Just $
+  case span isDigit etc of
+  ("",_)      -> error "readQ: the impossible happened"
+  (i,'.':etc) -> case span isDigit etc of
+                 (j,'e':'-':e:tc) | isDigit e ->
+                   read (i++j) % 10 ^ (length j + read (e:takeWhile isDigit tc))
+                 (j,'e':e:tc) | isDigit e ->
+                   read (i++j) * 10 ^ (read (e:takeWhile isDigit tc)) % 10 ^ length j
+                 (j,etc) -> read (i++j) % 10 ^ length j
+  (i,'%':e:tc) | isDigit e -> case span isDigit (e:tc) of
+                              (j,etc) -> read i % read j
+  (i,' ':'%':' ':e:tc) | isDigit e -> case span isDigit (e:tc) of
+                                      (j,etc) -> read i % read j
+  (i,etc)     -> read i % 1
+
+readQ :: String -> Quotient
+readQ = fromMaybe (error "No number to read") . maybeReadQ
+
+-- | Given a quotient (rational number),
+--   returns a tuple with
+--   its integer part,
+--   its fractional digits and
+--   the period size (last fractional digits).
+--   The signal is ignored.
+--
+-- > > digits 10 (1234567 / 100)
+-- > Right ([1,2,3,4,5],[6,7],[])
+-- > > digits 10 (1/3)
+-- > Right ([0],[3],1)
+-- > > digits 10 (1/6)
+-- > Right ([0],[1,6],1)
+-- > > digits 10 (1/7)
+-- > Right ([0],[1,4,2,8,5,7],6)
+-- > > digits 10 (1/11)
+-- > Right ([0],[0,9],2)
+-- > digits 10 (1/12)
+-- > Right ([0],[0,8,3],1)
+-- > > digits 10 (1/13)
+-- > Right ([0],[0,7,6,9,2,3],6)
+-- > > digits 10 123
+-- > Right ([1,2,3],[],[])
+-- > > digits 10 (-4/3)
+-- > Right ([1],[],[3])
+-- > > digits 10 (-1/3)
+-- > Right ([0],[],[3])
+digits :: Int -> Quotient -> Either String ([Int],[Int],[Int])
+digits b (0 :% 0) = Left "NaN"
+digits b (n :% 0) = Left "Infinity"
+digits b q = Right (ids,fds,pds)
+  where
+  (i,q') = properFraction q
+  (fds,pds) = fracDigits b q'
+  ids = case integerToDigits b i of
+        [] -> [0]
+        ds -> ds
+
+-- | Givent a base, returns the fractional digits of a Quotient (including a
+--   period if present).
+--
+-- > > fracDigits 10 (123 / 100)
+-- > ([2,3],[])
+-- > > fracDigits 10 (12345 / 100)
+-- > ([4,5],[])
+-- > > fracDigits 10 (12345 / 10)
+-- > ([5],[])
+-- > > fracDigits 10 (100 / 10)
+-- > ([],[])
+-- > > fracDigits 10 (1 / 3)
+-- > ([],[3])
+-- > > fracDigits 10 (1 / 7)
+-- > ([],[1,4,2,8,5,7])
+fracDigits :: Int -> Quotient -> ([Int],[Int])
+fracDigits b q | q < 0  = fracDigits b (abs q)
+fracDigits b q | q >= 1 = fracDigits b (snd $ properFraction q)
+fracDigits b q = let (fds,psz) = fun [] q
+                     fsz = length fds - psz
+                 in splitAt fsz fds
+  where
+  fun :: [(Integer,Integer)] -> Quotient -> ([Int],Int)
+  fun hist (0 :% _) = ([],0)
+  fun hist (x :% y) = case findIndex (==(x,y)) hist of
+                      Nothing -> (fromInteger q:fds,psz)
+                      Just i -> ([],i+1)
+    where
+    (q,r) = (x * toInteger b) `quotRem` y
+    (fds,psz) = fun ((x,y):hist) (r % y)
diff --git a/src/Text/PercentFormat/Spec.hs b/src/Text/PercentFormat/Spec.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/PercentFormat/Spec.hs
@@ -0,0 +1,88 @@
+-- |
+-- Module      : Text.PercentFormat
+-- Copyright   : (c) 2016-2018 Rudy Matela
+-- License     : 3-Clause BSD  (see the file LICENSE)
+-- Maintainer  : Rudy Matela <rudy@matela.com.br>
+--
+-- This file is part of PercentFormat a library for printf-style string
+-- formatting.
+--
+-- This module provides the Spec type which represents a @%...@ specification
+-- format.
+module Text.PercentFormat.Spec where
+
+import Data.Char (isDigit)
+
+data Spec = Spec
+  { ty        :: SpecType
+  , width     :: Int
+  , leftAlign :: Bool
+  , padWith   :: Char
+  , base      :: Int       -- ^ only for Number types
+  , precision :: Maybe Int -- ^ only for Number types, Nothing for infinite
+  , minPrecision :: Int    -- ^ minimum precision to show
+  , positivePrefix :: String
+  , capitalizeDigits :: Bool -- ^ whether to capitalize (hex) digits
+  }
+  deriving (Eq, Show)
+
+data SpecType = NumberSpec
+              | ReprSpec
+              | StringSpec
+              | CharSpec
+              | Percent
+  deriving (Eq, Show)
+
+spec :: Spec
+spec = Spec
+  { ty        = error "undefined Spec ty"
+  , width     = 0
+  , leftAlign = False
+  , padWith   = ' '
+  , base      = 10
+  , precision = Nothing
+  , minPrecision = 0
+  , positivePrefix = ""
+  , capitalizeDigits = False
+  }
+
+parseSpec :: String -> (Spec,String)
+parseSpec ('%':cs) = (spec {ty = Percent   }, cs)
+parseSpec ('r':cs) = (spec {ty = ReprSpec  }, cs)
+parseSpec ('s':cs) = (spec {ty = StringSpec}, cs)
+parseSpec ('c':cs) = (spec {ty = CharSpec  }, cs)
+parseSpec ('i':cs) = (spec {ty = NumberSpec, precision = Just 0}, cs)
+parseSpec ('d':cs) = (spec {ty = NumberSpec}, cs)
+parseSpec ('x':cs) = (spec {ty = NumberSpec, base = 16}, cs)
+parseSpec ('X':cs) = (spec {ty = NumberSpec, base = 16, capitalizeDigits = True}, cs)
+parseSpec ('o':cs) = (spec {ty = NumberSpec, base =  8}, cs)
+parseSpec ('b':cs) = (spec {ty = NumberSpec, base =  2}, cs)
+parseSpec ('f':cs) = (spec {ty = NumberSpec, minPrecision = 1}, cs)
+parseSpec ('q':cs) = error $ "`q' format will be implemented in a future version"
+parseSpec ('e':cs) = error $ "`e' format will be implemented in a future version"
+parseSpec ('E':cs) = error $ "`E' format will be implemented in a future version"
+parseSpec ('0':cs) = (s {padWith = '0'}, cs') where (s,cs') = parseSpec cs
+parseSpec ( n :cs) | isDigit n = let (w,cs') = span isDigit (n:cs)
+                                     (s,cs'') = parseSpec cs'
+                                 in (s {width = read w}, cs'')
+parseSpec ('.':'*':cs) = let (s,cs') = parseSpec cs
+                         in (s {precision = Nothing}, cs')
+parseSpec ('.':cs)             = let (w,cs')  = span isDigit cs
+                                     (s,cs'') = parseSpec cs'
+                                 in (s {precision = Just (read ('0':w))}, cs'')
+parseSpec ('-':cs) = (s {leftAlign = True}, cs')     where (s,cs') = parseSpec cs
+parseSpec (' ':cs) = (s {positivePrefix = " "}, cs') where (s,cs') = parseSpec cs
+parseSpec ('+':cs) = (s {positivePrefix = "+"}, cs') where (s,cs') = parseSpec cs
+parseSpec (c:_)    = error $ "unknown format string `" ++ (c:"'")
+-- NOTE: for some reason:
+-- > reads "4a" :: [(Int,String)]
+-- [(4,"a")]
+-- > reads "4." :: [(Int,String)]
+-- [(4,".")]
+-- > reads "4.0" :: [(Int,String)]
+-- []
+-- > reads "4.1" :: [(Int,String)]
+-- []
+-- > reads "4..1" :: [(Int,String)]
+-- [(4,"..1")]
+-- that's why I am using takeWhile above.
diff --git a/src/Text/PercentFormat/Utils.hs b/src/Text/PercentFormat/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/PercentFormat/Utils.hs
@@ -0,0 +1,105 @@
+-- |
+-- Module      : Text.PercentFormat
+-- Copyright   : (c) 2016-2018 Rudy Matela
+-- License     : 3-Clause BSD  (see the file LICENSE)
+-- Maintainer  : Rudy Matela <rudy@matela.com.br>
+--
+-- This file is part of PercentFormat a library for printf-style string
+-- formatting.
+--
+-- This module provides miscellaneous utility functions.
+module Text.PercentFormat.Utils
+  ( maybeRead
+  , align
+  , rightAlign
+  , leftAlign
+  , showWithBase
+  , applyWhen
+  , intsToDigits
+  , theLast
+  , loop
+  , none
+  , integerToDigits
+  )
+where
+
+-- TODO: document this module more thoroughly
+
+import Data.Maybe (listToMaybe)
+import Data.List (unfoldr)
+import Data.Char (intToDigit)
+
+-- | Reads a value encoded as a string,
+--   return 'Just' the value or 'Nothing' on error.
+maybeRead :: Read a => String -> Maybe a
+-- TODO: Use readsPrec to implement this.
+maybeRead = listToMaybe . map fst . reads
+
+align :: Bool -> Char -> Int -> String -> String
+align left = if left
+               then leftAlign
+               else rightAlign
+
+-- | @rightAlign c w s@ aligns 'String' @s@ to the right
+--   in a field of width @w@ using 'Char' @c@ as padding.
+--
+-- > right ' ' 5 "123"
+-- "  123"
+rightAlign :: Char -> Int -> String -> String
+rightAlign c width s | width <= len = s
+                     | otherwise    = replicate (width - len) c ++ s
+  where
+  len = length s
+
+-- | @left c w s@ aligns 'String' @s@ to the left
+--   in a field of width @w@ using 'Char' @c@ as padding.
+--
+-- > left ' ' 5 "123"
+-- "123  "
+leftAlign :: Char -> Int -> String -> String
+leftAlign c width s | width <= len = s
+                    | otherwise    = s ++ replicate (width - len) c
+  where
+  len = length s
+
+-- | @showWithBase b n@ returns a string representation of @n@ in base @b@.
+--
+-- > showWithBase 2 10
+-- "1010"
+-- > showWithBase 16 49406
+-- "c0f3"
+-- > showWithBase 10 (-1234)
+-- "-1234"
+showWithBase :: Integral a => Int -> a -> String
+showWithBase b 0 = "0"
+showWithBase b n | n < 0     = '-':showWithBase b (abs n)
+                 | otherwise = map intToDigit $ integerToDigits b n
+
+-- | Given an integer, returns a list of digits.  Signal is ignored.
+integerToDigits :: Integral a => Int -> a -> [Int]
+integerToDigits b =
+    map fromIntegral
+  . reverse
+  . unfoldr (\n -> listToMaybe [swap $ n `divMod` fromIntegral b | n /= 0])
+  . abs
+  where
+  swap (x,y) = (y,x)  -- not available on Hugs
+
+
+applyWhen :: Bool -> (a -> a) -> a -> a
+applyWhen True  f x = f x
+applyWhen False f x = x
+
+intsToDigits :: [Int] -> String
+intsToDigits = map intToDigit
+
+theLast :: Int -> [a] -> [a]
+theLast n xs = drop (length xs - n) xs
+
+-- | Like cycle, but return an empty list when the source list is empty.
+loop :: [a] -> [a]
+loop [] = []
+loop xs = cycle xs
+
+none :: (a -> Bool) -> [a] -> Bool
+none p = not . or . map p
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,11 @@
+# stack file for PercentFormat
+resolver: lts-11.11
+
+packages:
+- .
+
+extra-deps: []
+
+flags: {}
+
+extra-package-dbs: []
diff --git a/tests/Test.hs b/tests/Test.hs
new file mode 100644
--- /dev/null
+++ b/tests/Test.hs
@@ -0,0 +1,21 @@
+-- |
+-- Module      : Test
+-- Copyright   : (c) 2018 Rudy Matela
+-- License     : 3-Clause BSD  (see the file LICENSE)
+-- Maintainer  : Rudy Matela <rudy@matela.com.br>
+--
+-- This module is part of PercentFormat.
+--
+-- Some helper functios to test PercentFormat itself.
+module Test where
+
+import System.Environment (getArgs)
+import System.Exit (exitFailure)
+import Data.List (elemIndices)
+
+reportTests :: [Bool] -> IO ()
+reportTests tests =
+  case elemIndices False tests of
+    [] -> putStrLn "+++ Tests passed!"
+    is -> do putStrLn ("*** Failed tests:" ++ show is)
+             exitFailure
diff --git a/tests/diff/examples-+3i.out b/tests/diff/examples-+3i.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-+3i.out
@@ -0,0 +1,63 @@
+"%+3i" -% ""           == "!!!"
+"%+3i" -% "a"          == "!!!"
+"%+3i" -% "aa"         == "!!!"
+"%+3i" -% " "          == "!!!"
+"%+3i" -% "aaa"        == "!!!"
+"%+3i" -% "a "         == "!!!"
+"%+3i" -% " a"         == "!!!"
+"%+3i" -% "b"          == "!!!"
+"%+3i" -% "aaaa"       == "!!!"
+"%+3i" -% "aa "        == "!!!"
+"%+3i" -% "a a"        == "!!!"
+"%+3i" -% "ab"         == "!!!"
+"%+3i" -% 0            == " +0"
+"%+3i" -% 1            == " +1"
+"%+3i" -% (-1)         == " -1"
+"%+3i" -% 2            == " +2"
+"%+3i" -% (-2)         == " -2"
+"%+3i" -% 3            == " +3"
+"%+3i" -% (-3)         == " -3"
+"%+3i" -% 4            == " +4"
+"%+3i" -% (-4)         == " -4"
+"%+3i" -% 5            == " +5"
+"%+3i" -% (-5)         == " -5"
+"%+3i" -% 6            == " +6"
+"%+3i" -% 0.0          == " +0"
+"%+3i" -% 1.0          == " +1"
+"%+3i" -% 0.5          == " +0"
+"%+3i" -% (-1.0)       == " -1"
+"%+3i" -% 1.5          == " +2"
+"%+3i" -% 0.25         == " +0"
+"%+3i" -% Infinity     == "+Infinity"
+"%+3i" -% 2.0          == " +2"
+"%+3i" -% (-0.5)       == " -0"
+"%+3i" -% 1.25         == " +1"
+"%+3i" -% 0.75         == " +1"
+"%+3i" -% (-Infinity)  == "-Infinity"
+"%+3i" -% (0 % 1)      == " +0"
+"%+3i" -% (1 % 1)      == " +1"
+"%+3i" -% ((-1) % 1)   == " -1"
+"%+3i" -% (1 % 2)      == " +0"
+"%+3i" -% (2 % 1)      == " +2"
+"%+3i" -% ((-1) % 2)   == " -0"
+"%+3i" -% ((-2) % 1)   == " -2"
+"%+3i" -% (1 % 3)      == " +0"
+"%+3i" -% (3 % 1)      == " +3"
+"%+3i" -% ((-1) % 3)   == " -0"
+"%+3i" -% ((-3) % 1)   == " -3"
+"%+3i" -% (1 % 4)      == " +0"
+"%+3i" -% 'a'          == "!!!"
+"%+3i" -% ' '          == "!!!"
+"%+3i" -% 'b'          == "!!!"
+"%+3i" -% 'A'          == "!!!"
+"%+3i" -% 'c'          == "!!!"
+"%+3i" -% '\n'         == "!!!"
+"%+3i" -% 'd'          == "!!!"
+"%+3i" -% '0'          == "!!!"
+"%+3i" -% 'e'          == "!!!"
+"%+3i" -% 'B'          == "!!!"
+"%+3i" -% 'f'          == "!!!"
+"%+3i" -% '!'          == "!!!"
+"%+3i" -% False        == "!!!"
+"%+3i" -% True         == "!!!"
+
diff --git a/tests/diff/examples-+5f.out b/tests/diff/examples-+5f.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-+5f.out
@@ -0,0 +1,63 @@
+"%+5f" -% ""           == "!!!!!"
+"%+5f" -% "a"          == "!!!!!"
+"%+5f" -% "aa"         == "!!!!!"
+"%+5f" -% " "          == "!!!!!"
+"%+5f" -% "aaa"        == "!!!!!"
+"%+5f" -% "a "         == "!!!!!"
+"%+5f" -% " a"         == "!!!!!"
+"%+5f" -% "b"          == "!!!!!"
+"%+5f" -% "aaaa"       == "!!!!!"
+"%+5f" -% "aa "        == "!!!!!"
+"%+5f" -% "a a"        == "!!!!!"
+"%+5f" -% "ab"         == "!!!!!"
+"%+5f" -% 0            == " +0.0"
+"%+5f" -% 1            == " +1.0"
+"%+5f" -% (-1)         == " -1.0"
+"%+5f" -% 2            == " +2.0"
+"%+5f" -% (-2)         == " -2.0"
+"%+5f" -% 3            == " +3.0"
+"%+5f" -% (-3)         == " -3.0"
+"%+5f" -% 4            == " +4.0"
+"%+5f" -% (-4)         == " -4.0"
+"%+5f" -% 5            == " +5.0"
+"%+5f" -% (-5)         == " -5.0"
+"%+5f" -% 6            == " +6.0"
+"%+5f" -% 0.0          == " +0.0"
+"%+5f" -% 1.0          == " +1.0"
+"%+5f" -% 0.5          == " +0.5"
+"%+5f" -% (-1.0)       == " -1.0"
+"%+5f" -% 1.5          == " +1.5"
+"%+5f" -% 0.25         == "+0.25"
+"%+5f" -% Infinity     == "+Infinity"
+"%+5f" -% 2.0          == " +2.0"
+"%+5f" -% (-0.5)       == " -0.5"
+"%+5f" -% 1.25         == "+1.25"
+"%+5f" -% 0.75         == "+0.75"
+"%+5f" -% (-Infinity)  == "-Infinity"
+"%+5f" -% (0 % 1)      == " +0.0"
+"%+5f" -% (1 % 1)      == " +1.0"
+"%+5f" -% ((-1) % 1)   == " -1.0"
+"%+5f" -% (1 % 2)      == " +0.5"
+"%+5f" -% (2 % 1)      == " +2.0"
+"%+5f" -% ((-1) % 2)   == " -0.5"
+"%+5f" -% ((-2) % 1)   == " -2.0"
+"%+5f" -% (1 % 3)      == "   +0"
+"%+5f" -% (3 % 1)      == " +3.0"
+"%+5f" -% ((-1) % 3)   == "   -0"
+"%+5f" -% ((-3) % 1)   == " -3.0"
+"%+5f" -% (1 % 4)      == "+0.25"
+"%+5f" -% 'a'          == "!!!!!"
+"%+5f" -% ' '          == "!!!!!"
+"%+5f" -% 'b'          == "!!!!!"
+"%+5f" -% 'A'          == "!!!!!"
+"%+5f" -% 'c'          == "!!!!!"
+"%+5f" -% '\n'         == "!!!!!"
+"%+5f" -% 'd'          == "!!!!!"
+"%+5f" -% '0'          == "!!!!!"
+"%+5f" -% 'e'          == "!!!!!"
+"%+5f" -% 'B'          == "!!!!!"
+"%+5f" -% 'f'          == "!!!!!"
+"%+5f" -% '!'          == "!!!!!"
+"%+5f" -% False        == "!!!!!"
+"%+5f" -% True         == "!!!!!"
+
diff --git a/tests/diff/examples--11p2f.out b/tests/diff/examples--11p2f.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples--11p2f.out
@@ -0,0 +1,63 @@
+"%-11.2f" -% ""           == "!!!!!!!!!!!"
+"%-11.2f" -% "a"          == "!!!!!!!!!!!"
+"%-11.2f" -% "aa"         == "!!!!!!!!!!!"
+"%-11.2f" -% " "          == "!!!!!!!!!!!"
+"%-11.2f" -% "aaa"        == "!!!!!!!!!!!"
+"%-11.2f" -% "a "         == "!!!!!!!!!!!"
+"%-11.2f" -% " a"         == "!!!!!!!!!!!"
+"%-11.2f" -% "b"          == "!!!!!!!!!!!"
+"%-11.2f" -% "aaaa"       == "!!!!!!!!!!!"
+"%-11.2f" -% "aa "        == "!!!!!!!!!!!"
+"%-11.2f" -% "a a"        == "!!!!!!!!!!!"
+"%-11.2f" -% "ab"         == "!!!!!!!!!!!"
+"%-11.2f" -% 0            == "0.00       "
+"%-11.2f" -% 1            == "1.00       "
+"%-11.2f" -% (-1)         == "-1.00      "
+"%-11.2f" -% 2            == "2.00       "
+"%-11.2f" -% (-2)         == "-2.00      "
+"%-11.2f" -% 3            == "3.00       "
+"%-11.2f" -% (-3)         == "-3.00      "
+"%-11.2f" -% 4            == "4.00       "
+"%-11.2f" -% (-4)         == "-4.00      "
+"%-11.2f" -% 5            == "5.00       "
+"%-11.2f" -% (-5)         == "-5.00      "
+"%-11.2f" -% 6            == "6.00       "
+"%-11.2f" -% 0.0          == "0.00       "
+"%-11.2f" -% 1.0          == "1.00       "
+"%-11.2f" -% 0.5          == "0.50       "
+"%-11.2f" -% (-1.0)       == "-1.00      "
+"%-11.2f" -% 1.5          == "1.50       "
+"%-11.2f" -% 0.25         == "0.25       "
+"%-11.2f" -% Infinity     == "Infinity   "
+"%-11.2f" -% 2.0          == "2.00       "
+"%-11.2f" -% (-0.5)       == "-0.50      "
+"%-11.2f" -% 1.25         == "1.25       "
+"%-11.2f" -% 0.75         == "0.75       "
+"%-11.2f" -% (-Infinity)  == "-Infinity  "
+"%-11.2f" -% (0 % 1)      == "0.00       "
+"%-11.2f" -% (1 % 1)      == "1.00       "
+"%-11.2f" -% ((-1) % 1)   == "-1.00      "
+"%-11.2f" -% (1 % 2)      == "0.50       "
+"%-11.2f" -% (2 % 1)      == "2.00       "
+"%-11.2f" -% ((-1) % 2)   == "-0.50      "
+"%-11.2f" -% ((-2) % 1)   == "-2.00      "
+"%-11.2f" -% (1 % 3)      == "0.33       "
+"%-11.2f" -% (3 % 1)      == "3.00       "
+"%-11.2f" -% ((-1) % 3)   == "-0.33      "
+"%-11.2f" -% ((-3) % 1)   == "-3.00      "
+"%-11.2f" -% (1 % 4)      == "0.25       "
+"%-11.2f" -% 'a'          == "!!!!!!!!!!!"
+"%-11.2f" -% ' '          == "!!!!!!!!!!!"
+"%-11.2f" -% 'b'          == "!!!!!!!!!!!"
+"%-11.2f" -% 'A'          == "!!!!!!!!!!!"
+"%-11.2f" -% 'c'          == "!!!!!!!!!!!"
+"%-11.2f" -% '\n'         == "!!!!!!!!!!!"
+"%-11.2f" -% 'd'          == "!!!!!!!!!!!"
+"%-11.2f" -% '0'          == "!!!!!!!!!!!"
+"%-11.2f" -% 'e'          == "!!!!!!!!!!!"
+"%-11.2f" -% 'B'          == "!!!!!!!!!!!"
+"%-11.2f" -% 'f'          == "!!!!!!!!!!!"
+"%-11.2f" -% '!'          == "!!!!!!!!!!!"
+"%-11.2f" -% False        == "!!!!!!!!!!!"
+"%-11.2f" -% True         == "!!!!!!!!!!!"
+
diff --git a/tests/diff/examples--3i.out b/tests/diff/examples--3i.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples--3i.out
@@ -0,0 +1,63 @@
+"%-3i" -% ""           == "!!!"
+"%-3i" -% "a"          == "!!!"
+"%-3i" -% "aa"         == "!!!"
+"%-3i" -% " "          == "!!!"
+"%-3i" -% "aaa"        == "!!!"
+"%-3i" -% "a "         == "!!!"
+"%-3i" -% " a"         == "!!!"
+"%-3i" -% "b"          == "!!!"
+"%-3i" -% "aaaa"       == "!!!"
+"%-3i" -% "aa "        == "!!!"
+"%-3i" -% "a a"        == "!!!"
+"%-3i" -% "ab"         == "!!!"
+"%-3i" -% 0            == "0  "
+"%-3i" -% 1            == "1  "
+"%-3i" -% (-1)         == "-1 "
+"%-3i" -% 2            == "2  "
+"%-3i" -% (-2)         == "-2 "
+"%-3i" -% 3            == "3  "
+"%-3i" -% (-3)         == "-3 "
+"%-3i" -% 4            == "4  "
+"%-3i" -% (-4)         == "-4 "
+"%-3i" -% 5            == "5  "
+"%-3i" -% (-5)         == "-5 "
+"%-3i" -% 6            == "6  "
+"%-3i" -% 0.0          == "0  "
+"%-3i" -% 1.0          == "1  "
+"%-3i" -% 0.5          == "0  "
+"%-3i" -% (-1.0)       == "-1 "
+"%-3i" -% 1.5          == "2  "
+"%-3i" -% 0.25         == "0  "
+"%-3i" -% Infinity     == "Infinity"
+"%-3i" -% 2.0          == "2  "
+"%-3i" -% (-0.5)       == "-0 "
+"%-3i" -% 1.25         == "1  "
+"%-3i" -% 0.75         == "1  "
+"%-3i" -% (-Infinity)  == "-Infinity"
+"%-3i" -% (0 % 1)      == "0  "
+"%-3i" -% (1 % 1)      == "1  "
+"%-3i" -% ((-1) % 1)   == "-1 "
+"%-3i" -% (1 % 2)      == "0  "
+"%-3i" -% (2 % 1)      == "2  "
+"%-3i" -% ((-1) % 2)   == "-0 "
+"%-3i" -% ((-2) % 1)   == "-2 "
+"%-3i" -% (1 % 3)      == "0  "
+"%-3i" -% (3 % 1)      == "3  "
+"%-3i" -% ((-1) % 3)   == "-0 "
+"%-3i" -% ((-3) % 1)   == "-3 "
+"%-3i" -% (1 % 4)      == "0  "
+"%-3i" -% 'a'          == "!!!"
+"%-3i" -% ' '          == "!!!"
+"%-3i" -% 'b'          == "!!!"
+"%-3i" -% 'A'          == "!!!"
+"%-3i" -% 'c'          == "!!!"
+"%-3i" -% '\n'         == "!!!"
+"%-3i" -% 'd'          == "!!!"
+"%-3i" -% '0'          == "!!!"
+"%-3i" -% 'e'          == "!!!"
+"%-3i" -% 'B'          == "!!!"
+"%-3i" -% 'f'          == "!!!"
+"%-3i" -% '!'          == "!!!"
+"%-3i" -% False        == "!!!"
+"%-3i" -% True         == "!!!"
+
diff --git a/tests/diff/examples--5f.out b/tests/diff/examples--5f.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples--5f.out
@@ -0,0 +1,63 @@
+"%-5f" -% ""           == "!!!!!"
+"%-5f" -% "a"          == "!!!!!"
+"%-5f" -% "aa"         == "!!!!!"
+"%-5f" -% " "          == "!!!!!"
+"%-5f" -% "aaa"        == "!!!!!"
+"%-5f" -% "a "         == "!!!!!"
+"%-5f" -% " a"         == "!!!!!"
+"%-5f" -% "b"          == "!!!!!"
+"%-5f" -% "aaaa"       == "!!!!!"
+"%-5f" -% "aa "        == "!!!!!"
+"%-5f" -% "a a"        == "!!!!!"
+"%-5f" -% "ab"         == "!!!!!"
+"%-5f" -% 0            == "0.0  "
+"%-5f" -% 1            == "1.0  "
+"%-5f" -% (-1)         == "-1.0 "
+"%-5f" -% 2            == "2.0  "
+"%-5f" -% (-2)         == "-2.0 "
+"%-5f" -% 3            == "3.0  "
+"%-5f" -% (-3)         == "-3.0 "
+"%-5f" -% 4            == "4.0  "
+"%-5f" -% (-4)         == "-4.0 "
+"%-5f" -% 5            == "5.0  "
+"%-5f" -% (-5)         == "-5.0 "
+"%-5f" -% 6            == "6.0  "
+"%-5f" -% 0.0          == "0.0  "
+"%-5f" -% 1.0          == "1.0  "
+"%-5f" -% 0.5          == "0.5  "
+"%-5f" -% (-1.0)       == "-1.0 "
+"%-5f" -% 1.5          == "1.5  "
+"%-5f" -% 0.25         == "0.25 "
+"%-5f" -% Infinity     == "Infinity"
+"%-5f" -% 2.0          == "2.0  "
+"%-5f" -% (-0.5)       == "-0.5 "
+"%-5f" -% 1.25         == "1.25 "
+"%-5f" -% 0.75         == "0.75 "
+"%-5f" -% (-Infinity)  == "-Infinity"
+"%-5f" -% (0 % 1)      == "0.0  "
+"%-5f" -% (1 % 1)      == "1.0  "
+"%-5f" -% ((-1) % 1)   == "-1.0 "
+"%-5f" -% (1 % 2)      == "0.5  "
+"%-5f" -% (2 % 1)      == "2.0  "
+"%-5f" -% ((-1) % 2)   == "-0.5 "
+"%-5f" -% ((-2) % 1)   == "-2.0 "
+"%-5f" -% (1 % 3)      == "0    "
+"%-5f" -% (3 % 1)      == "3.0  "
+"%-5f" -% ((-1) % 3)   == "-0   "
+"%-5f" -% ((-3) % 1)   == "-3.0 "
+"%-5f" -% (1 % 4)      == "0.25 "
+"%-5f" -% 'a'          == "!!!!!"
+"%-5f" -% ' '          == "!!!!!"
+"%-5f" -% 'b'          == "!!!!!"
+"%-5f" -% 'A'          == "!!!!!"
+"%-5f" -% 'c'          == "!!!!!"
+"%-5f" -% '\n'         == "!!!!!"
+"%-5f" -% 'd'          == "!!!!!"
+"%-5f" -% '0'          == "!!!!!"
+"%-5f" -% 'e'          == "!!!!!"
+"%-5f" -% 'B'          == "!!!!!"
+"%-5f" -% 'f'          == "!!!!!"
+"%-5f" -% '!'          == "!!!!!"
+"%-5f" -% False        == "!!!!!"
+"%-5f" -% True         == "!!!!!"
+
diff --git a/tests/diff/examples--6r.out b/tests/diff/examples--6r.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples--6r.out
@@ -0,0 +1,63 @@
+"%-6r" -% ""           == "\"\"    "
+"%-6r" -% "a"          == "\"a\"   "
+"%-6r" -% "aa"         == "\"aa\"  "
+"%-6r" -% " "          == "\" \"   "
+"%-6r" -% "aaa"        == "\"aaa\" "
+"%-6r" -% "a "         == "\"a \"  "
+"%-6r" -% " a"         == "\" a\"  "
+"%-6r" -% "b"          == "\"b\"   "
+"%-6r" -% "aaaa"       == "\"aaaa\""
+"%-6r" -% "aa "        == "\"aa \" "
+"%-6r" -% "a a"        == "\"a a\" "
+"%-6r" -% "ab"         == "\"ab\"  "
+"%-6r" -% 0            == "0     "
+"%-6r" -% 1            == "1     "
+"%-6r" -% (-1)         == "-1    "
+"%-6r" -% 2            == "2     "
+"%-6r" -% (-2)         == "-2    "
+"%-6r" -% 3            == "3     "
+"%-6r" -% (-3)         == "-3    "
+"%-6r" -% 4            == "4     "
+"%-6r" -% (-4)         == "-4    "
+"%-6r" -% 5            == "5     "
+"%-6r" -% (-5)         == "-5    "
+"%-6r" -% 6            == "6     "
+"%-6r" -% 0.0          == "0.0   "
+"%-6r" -% 1.0          == "1.0   "
+"%-6r" -% 0.5          == "0.5   "
+"%-6r" -% (-1.0)       == "-1.0  "
+"%-6r" -% 1.5          == "1.5   "
+"%-6r" -% 0.25         == "0.25  "
+"%-6r" -% Infinity     == "Infinity"
+"%-6r" -% 2.0          == "2.0   "
+"%-6r" -% (-0.5)       == "-0.5  "
+"%-6r" -% 1.25         == "1.25  "
+"%-6r" -% 0.75         == "0.75  "
+"%-6r" -% (-Infinity)  == "-Infinity"
+"%-6r" -% (0 % 1)      == "0 % 1 "
+"%-6r" -% (1 % 1)      == "1 % 1 "
+"%-6r" -% ((-1) % 1)   == "(-1) % 1"
+"%-6r" -% (1 % 2)      == "1 % 2 "
+"%-6r" -% (2 % 1)      == "2 % 1 "
+"%-6r" -% ((-1) % 2)   == "(-1) % 2"
+"%-6r" -% ((-2) % 1)   == "(-2) % 1"
+"%-6r" -% (1 % 3)      == "1 % 3 "
+"%-6r" -% (3 % 1)      == "3 % 1 "
+"%-6r" -% ((-1) % 3)   == "(-1) % 3"
+"%-6r" -% ((-3) % 1)   == "(-3) % 1"
+"%-6r" -% (1 % 4)      == "1 % 4 "
+"%-6r" -% 'a'          == "'a'   "
+"%-6r" -% ' '          == "' '   "
+"%-6r" -% 'b'          == "'b'   "
+"%-6r" -% 'A'          == "'A'   "
+"%-6r" -% 'c'          == "'c'   "
+"%-6r" -% '\n'         == "'\\n'  "
+"%-6r" -% 'd'          == "'d'   "
+"%-6r" -% '0'          == "'0'   "
+"%-6r" -% 'e'          == "'e'   "
+"%-6r" -% 'B'          == "'B'   "
+"%-6r" -% 'f'          == "'f'   "
+"%-6r" -% '!'          == "'!'   "
+"%-6r" -% False        == "False "
+"%-6r" -% True         == "True  "
+
diff --git a/tests/diff/examples--6s.out b/tests/diff/examples--6s.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples--6s.out
@@ -0,0 +1,63 @@
+"%-6s" -% ""           == "      "
+"%-6s" -% "a"          == "a     "
+"%-6s" -% "aa"         == "aa    "
+"%-6s" -% " "          == "      "
+"%-6s" -% "aaa"        == "aaa   "
+"%-6s" -% "a "         == "a     "
+"%-6s" -% " a"         == " a    "
+"%-6s" -% "b"          == "b     "
+"%-6s" -% "aaaa"       == "aaaa  "
+"%-6s" -% "aa "        == "aa    "
+"%-6s" -% "a a"        == "a a   "
+"%-6s" -% "ab"         == "ab    "
+"%-6s" -% 0            == "0     "
+"%-6s" -% 1            == "1     "
+"%-6s" -% (-1)         == "-1    "
+"%-6s" -% 2            == "2     "
+"%-6s" -% (-2)         == "-2    "
+"%-6s" -% 3            == "3     "
+"%-6s" -% (-3)         == "-3    "
+"%-6s" -% 4            == "4     "
+"%-6s" -% (-4)         == "-4    "
+"%-6s" -% 5            == "5     "
+"%-6s" -% (-5)         == "-5    "
+"%-6s" -% 6            == "6     "
+"%-6s" -% 0.0          == "0.0   "
+"%-6s" -% 1.0          == "1.0   "
+"%-6s" -% 0.5          == "0.5   "
+"%-6s" -% (-1.0)       == "-1.0  "
+"%-6s" -% 1.5          == "1.5   "
+"%-6s" -% 0.25         == "0.25  "
+"%-6s" -% Infinity     == "Infinity"
+"%-6s" -% 2.0          == "2.0   "
+"%-6s" -% (-0.5)       == "-0.5  "
+"%-6s" -% 1.25         == "1.25  "
+"%-6s" -% 0.75         == "0.75  "
+"%-6s" -% (-Infinity)  == "-Infinity"
+"%-6s" -% (0 % 1)      == "0 % 1 "
+"%-6s" -% (1 % 1)      == "1 % 1 "
+"%-6s" -% ((-1) % 1)   == "(-1) % 1"
+"%-6s" -% (1 % 2)      == "1 % 2 "
+"%-6s" -% (2 % 1)      == "2 % 1 "
+"%-6s" -% ((-1) % 2)   == "(-1) % 2"
+"%-6s" -% ((-2) % 1)   == "(-2) % 1"
+"%-6s" -% (1 % 3)      == "1 % 3 "
+"%-6s" -% (3 % 1)      == "3 % 1 "
+"%-6s" -% ((-1) % 3)   == "(-1) % 3"
+"%-6s" -% ((-3) % 1)   == "(-3) % 1"
+"%-6s" -% (1 % 4)      == "1 % 4 "
+"%-6s" -% 'a'          == "'a'   "
+"%-6s" -% ' '          == "' '   "
+"%-6s" -% 'b'          == "'b'   "
+"%-6s" -% 'A'          == "'A'   "
+"%-6s" -% 'c'          == "'c'   "
+"%-6s" -% '\n'         == "'\\n'  "
+"%-6s" -% 'd'          == "'d'   "
+"%-6s" -% '0'          == "'0'   "
+"%-6s" -% 'e'          == "'e'   "
+"%-6s" -% 'B'          == "'B'   "
+"%-6s" -% 'f'          == "'f'   "
+"%-6s" -% '!'          == "'!'   "
+"%-6s" -% False        == "False "
+"%-6s" -% True         == "True  "
+
diff --git a/tests/diff/examples-03i.out b/tests/diff/examples-03i.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-03i.out
@@ -0,0 +1,63 @@
+"%03i" -% ""           == "!!!"
+"%03i" -% "a"          == "!!!"
+"%03i" -% "aa"         == "!!!"
+"%03i" -% " "          == "!!!"
+"%03i" -% "aaa"        == "!!!"
+"%03i" -% "a "         == "!!!"
+"%03i" -% " a"         == "!!!"
+"%03i" -% "b"          == "!!!"
+"%03i" -% "aaaa"       == "!!!"
+"%03i" -% "aa "        == "!!!"
+"%03i" -% "a a"        == "!!!"
+"%03i" -% "ab"         == "!!!"
+"%03i" -% 0            == "000"
+"%03i" -% 1            == "001"
+"%03i" -% (-1)         == "-01"
+"%03i" -% 2            == "002"
+"%03i" -% (-2)         == "-02"
+"%03i" -% 3            == "003"
+"%03i" -% (-3)         == "-03"
+"%03i" -% 4            == "004"
+"%03i" -% (-4)         == "-04"
+"%03i" -% 5            == "005"
+"%03i" -% (-5)         == "-05"
+"%03i" -% 6            == "006"
+"%03i" -% 0.0          == "000"
+"%03i" -% 1.0          == "001"
+"%03i" -% 0.5          == "000"
+"%03i" -% (-1.0)       == "-01"
+"%03i" -% 1.5          == "002"
+"%03i" -% 0.25         == "000"
+"%03i" -% Infinity     == "Infinity"
+"%03i" -% 2.0          == "002"
+"%03i" -% (-0.5)       == "-00"
+"%03i" -% 1.25         == "001"
+"%03i" -% 0.75         == "001"
+"%03i" -% (-Infinity)  == "-Infinity"
+"%03i" -% (0 % 1)      == "000"
+"%03i" -% (1 % 1)      == "001"
+"%03i" -% ((-1) % 1)   == "-01"
+"%03i" -% (1 % 2)      == "000"
+"%03i" -% (2 % 1)      == "002"
+"%03i" -% ((-1) % 2)   == "-00"
+"%03i" -% ((-2) % 1)   == "-02"
+"%03i" -% (1 % 3)      == "000"
+"%03i" -% (3 % 1)      == "003"
+"%03i" -% ((-1) % 3)   == "-00"
+"%03i" -% ((-3) % 1)   == "-03"
+"%03i" -% (1 % 4)      == "000"
+"%03i" -% 'a'          == "!!!"
+"%03i" -% ' '          == "!!!"
+"%03i" -% 'b'          == "!!!"
+"%03i" -% 'A'          == "!!!"
+"%03i" -% 'c'          == "!!!"
+"%03i" -% '\n'         == "!!!"
+"%03i" -% 'd'          == "!!!"
+"%03i" -% '0'          == "!!!"
+"%03i" -% 'e'          == "!!!"
+"%03i" -% 'B'          == "!!!"
+"%03i" -% 'f'          == "!!!"
+"%03i" -% '!'          == "!!!"
+"%03i" -% False        == "!!!"
+"%03i" -% True         == "!!!"
+
diff --git a/tests/diff/examples-11p2f.out b/tests/diff/examples-11p2f.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-11p2f.out
@@ -0,0 +1,63 @@
+"%11.2f" -% ""           == "!!!!!!!!!!!"
+"%11.2f" -% "a"          == "!!!!!!!!!!!"
+"%11.2f" -% "aa"         == "!!!!!!!!!!!"
+"%11.2f" -% " "          == "!!!!!!!!!!!"
+"%11.2f" -% "aaa"        == "!!!!!!!!!!!"
+"%11.2f" -% "a "         == "!!!!!!!!!!!"
+"%11.2f" -% " a"         == "!!!!!!!!!!!"
+"%11.2f" -% "b"          == "!!!!!!!!!!!"
+"%11.2f" -% "aaaa"       == "!!!!!!!!!!!"
+"%11.2f" -% "aa "        == "!!!!!!!!!!!"
+"%11.2f" -% "a a"        == "!!!!!!!!!!!"
+"%11.2f" -% "ab"         == "!!!!!!!!!!!"
+"%11.2f" -% 0            == "       0.00"
+"%11.2f" -% 1            == "       1.00"
+"%11.2f" -% (-1)         == "      -1.00"
+"%11.2f" -% 2            == "       2.00"
+"%11.2f" -% (-2)         == "      -2.00"
+"%11.2f" -% 3            == "       3.00"
+"%11.2f" -% (-3)         == "      -3.00"
+"%11.2f" -% 4            == "       4.00"
+"%11.2f" -% (-4)         == "      -4.00"
+"%11.2f" -% 5            == "       5.00"
+"%11.2f" -% (-5)         == "      -5.00"
+"%11.2f" -% 6            == "       6.00"
+"%11.2f" -% 0.0          == "       0.00"
+"%11.2f" -% 1.0          == "       1.00"
+"%11.2f" -% 0.5          == "       0.50"
+"%11.2f" -% (-1.0)       == "      -1.00"
+"%11.2f" -% 1.5          == "       1.50"
+"%11.2f" -% 0.25         == "       0.25"
+"%11.2f" -% Infinity     == "   Infinity"
+"%11.2f" -% 2.0          == "       2.00"
+"%11.2f" -% (-0.5)       == "      -0.50"
+"%11.2f" -% 1.25         == "       1.25"
+"%11.2f" -% 0.75         == "       0.75"
+"%11.2f" -% (-Infinity)  == "  -Infinity"
+"%11.2f" -% (0 % 1)      == "       0.00"
+"%11.2f" -% (1 % 1)      == "       1.00"
+"%11.2f" -% ((-1) % 1)   == "      -1.00"
+"%11.2f" -% (1 % 2)      == "       0.50"
+"%11.2f" -% (2 % 1)      == "       2.00"
+"%11.2f" -% ((-1) % 2)   == "      -0.50"
+"%11.2f" -% ((-2) % 1)   == "      -2.00"
+"%11.2f" -% (1 % 3)      == "       0.33"
+"%11.2f" -% (3 % 1)      == "       3.00"
+"%11.2f" -% ((-1) % 3)   == "      -0.33"
+"%11.2f" -% ((-3) % 1)   == "      -3.00"
+"%11.2f" -% (1 % 4)      == "       0.25"
+"%11.2f" -% 'a'          == "!!!!!!!!!!!"
+"%11.2f" -% ' '          == "!!!!!!!!!!!"
+"%11.2f" -% 'b'          == "!!!!!!!!!!!"
+"%11.2f" -% 'A'          == "!!!!!!!!!!!"
+"%11.2f" -% 'c'          == "!!!!!!!!!!!"
+"%11.2f" -% '\n'         == "!!!!!!!!!!!"
+"%11.2f" -% 'd'          == "!!!!!!!!!!!"
+"%11.2f" -% '0'          == "!!!!!!!!!!!"
+"%11.2f" -% 'e'          == "!!!!!!!!!!!"
+"%11.2f" -% 'B'          == "!!!!!!!!!!!"
+"%11.2f" -% 'f'          == "!!!!!!!!!!!"
+"%11.2f" -% '!'          == "!!!!!!!!!!!"
+"%11.2f" -% False        == "!!!!!!!!!!!"
+"%11.2f" -% True         == "!!!!!!!!!!!"
+
diff --git a/tests/diff/examples-2c.out b/tests/diff/examples-2c.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-2c.out
@@ -0,0 +1,63 @@
+"%2c" -% ""           == "!!"
+"%2c" -% "a"          == "!!"
+"%2c" -% "aa"         == "!!"
+"%2c" -% " "          == "!!"
+"%2c" -% "aaa"        == "!!"
+"%2c" -% "a "         == "!!"
+"%2c" -% " a"         == "!!"
+"%2c" -% "b"          == "!!"
+"%2c" -% "aaaa"       == "!!"
+"%2c" -% "aa "        == "!!"
+"%2c" -% "a a"        == "!!"
+"%2c" -% "ab"         == "!!"
+"%2c" -% 0            == "!!"
+"%2c" -% 1            == "!!"
+"%2c" -% (-1)         == "!!"
+"%2c" -% 2            == "!!"
+"%2c" -% (-2)         == "!!"
+"%2c" -% 3            == "!!"
+"%2c" -% (-3)         == "!!"
+"%2c" -% 4            == "!!"
+"%2c" -% (-4)         == "!!"
+"%2c" -% 5            == "!!"
+"%2c" -% (-5)         == "!!"
+"%2c" -% 6            == "!!"
+"%2c" -% 0.0          == "!!"
+"%2c" -% 1.0          == "!!"
+"%2c" -% 0.5          == "!!"
+"%2c" -% (-1.0)       == "!!"
+"%2c" -% 1.5          == "!!"
+"%2c" -% 0.25         == "!!"
+"%2c" -% Infinity     == "!!"
+"%2c" -% 2.0          == "!!"
+"%2c" -% (-0.5)       == "!!"
+"%2c" -% 1.25         == "!!"
+"%2c" -% 0.75         == "!!"
+"%2c" -% (-Infinity)  == "!!"
+"%2c" -% (0 % 1)      == "!!"
+"%2c" -% (1 % 1)      == "!!"
+"%2c" -% ((-1) % 1)   == "!!"
+"%2c" -% (1 % 2)      == "!!"
+"%2c" -% (2 % 1)      == "!!"
+"%2c" -% ((-1) % 2)   == "!!"
+"%2c" -% ((-2) % 1)   == "!!"
+"%2c" -% (1 % 3)      == "!!"
+"%2c" -% (3 % 1)      == "!!"
+"%2c" -% ((-1) % 3)   == "!!"
+"%2c" -% ((-3) % 1)   == "!!"
+"%2c" -% (1 % 4)      == "!!"
+"%2c" -% 'a'          == " a"
+"%2c" -% ' '          == "  "
+"%2c" -% 'b'          == " b"
+"%2c" -% 'A'          == " A"
+"%2c" -% 'c'          == " c"
+"%2c" -% '\n'         == " \n"
+"%2c" -% 'd'          == " d"
+"%2c" -% '0'          == " 0"
+"%2c" -% 'e'          == " e"
+"%2c" -% 'B'          == " B"
+"%2c" -% 'f'          == " f"
+"%2c" -% '!'          == " !"
+"%2c" -% False        == "!!"
+"%2c" -% True         == "!!"
+
diff --git a/tests/diff/examples-3i.out b/tests/diff/examples-3i.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-3i.out
@@ -0,0 +1,63 @@
+"%3i" -% ""           == "!!!"
+"%3i" -% "a"          == "!!!"
+"%3i" -% "aa"         == "!!!"
+"%3i" -% " "          == "!!!"
+"%3i" -% "aaa"        == "!!!"
+"%3i" -% "a "         == "!!!"
+"%3i" -% " a"         == "!!!"
+"%3i" -% "b"          == "!!!"
+"%3i" -% "aaaa"       == "!!!"
+"%3i" -% "aa "        == "!!!"
+"%3i" -% "a a"        == "!!!"
+"%3i" -% "ab"         == "!!!"
+"%3i" -% 0            == "  0"
+"%3i" -% 1            == "  1"
+"%3i" -% (-1)         == " -1"
+"%3i" -% 2            == "  2"
+"%3i" -% (-2)         == " -2"
+"%3i" -% 3            == "  3"
+"%3i" -% (-3)         == " -3"
+"%3i" -% 4            == "  4"
+"%3i" -% (-4)         == " -4"
+"%3i" -% 5            == "  5"
+"%3i" -% (-5)         == " -5"
+"%3i" -% 6            == "  6"
+"%3i" -% 0.0          == "  0"
+"%3i" -% 1.0          == "  1"
+"%3i" -% 0.5          == "  0"
+"%3i" -% (-1.0)       == " -1"
+"%3i" -% 1.5          == "  2"
+"%3i" -% 0.25         == "  0"
+"%3i" -% Infinity     == "Infinity"
+"%3i" -% 2.0          == "  2"
+"%3i" -% (-0.5)       == " -0"
+"%3i" -% 1.25         == "  1"
+"%3i" -% 0.75         == "  1"
+"%3i" -% (-Infinity)  == "-Infinity"
+"%3i" -% (0 % 1)      == "  0"
+"%3i" -% (1 % 1)      == "  1"
+"%3i" -% ((-1) % 1)   == " -1"
+"%3i" -% (1 % 2)      == "  0"
+"%3i" -% (2 % 1)      == "  2"
+"%3i" -% ((-1) % 2)   == " -0"
+"%3i" -% ((-2) % 1)   == " -2"
+"%3i" -% (1 % 3)      == "  0"
+"%3i" -% (3 % 1)      == "  3"
+"%3i" -% ((-1) % 3)   == " -0"
+"%3i" -% ((-3) % 1)   == " -3"
+"%3i" -% (1 % 4)      == "  0"
+"%3i" -% 'a'          == "!!!"
+"%3i" -% ' '          == "!!!"
+"%3i" -% 'b'          == "!!!"
+"%3i" -% 'A'          == "!!!"
+"%3i" -% 'c'          == "!!!"
+"%3i" -% '\n'         == "!!!"
+"%3i" -% 'd'          == "!!!"
+"%3i" -% '0'          == "!!!"
+"%3i" -% 'e'          == "!!!"
+"%3i" -% 'B'          == "!!!"
+"%3i" -% 'f'          == "!!!"
+"%3i" -% '!'          == "!!!"
+"%3i" -% False        == "!!!"
+"%3i" -% True         == "!!!"
+
diff --git a/tests/diff/examples-5f.out b/tests/diff/examples-5f.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-5f.out
@@ -0,0 +1,63 @@
+"%5f" -% ""           == "!!!!!"
+"%5f" -% "a"          == "!!!!!"
+"%5f" -% "aa"         == "!!!!!"
+"%5f" -% " "          == "!!!!!"
+"%5f" -% "aaa"        == "!!!!!"
+"%5f" -% "a "         == "!!!!!"
+"%5f" -% " a"         == "!!!!!"
+"%5f" -% "b"          == "!!!!!"
+"%5f" -% "aaaa"       == "!!!!!"
+"%5f" -% "aa "        == "!!!!!"
+"%5f" -% "a a"        == "!!!!!"
+"%5f" -% "ab"         == "!!!!!"
+"%5f" -% 0            == "  0.0"
+"%5f" -% 1            == "  1.0"
+"%5f" -% (-1)         == " -1.0"
+"%5f" -% 2            == "  2.0"
+"%5f" -% (-2)         == " -2.0"
+"%5f" -% 3            == "  3.0"
+"%5f" -% (-3)         == " -3.0"
+"%5f" -% 4            == "  4.0"
+"%5f" -% (-4)         == " -4.0"
+"%5f" -% 5            == "  5.0"
+"%5f" -% (-5)         == " -5.0"
+"%5f" -% 6            == "  6.0"
+"%5f" -% 0.0          == "  0.0"
+"%5f" -% 1.0          == "  1.0"
+"%5f" -% 0.5          == "  0.5"
+"%5f" -% (-1.0)       == " -1.0"
+"%5f" -% 1.5          == "  1.5"
+"%5f" -% 0.25         == " 0.25"
+"%5f" -% Infinity     == "Infinity"
+"%5f" -% 2.0          == "  2.0"
+"%5f" -% (-0.5)       == " -0.5"
+"%5f" -% 1.25         == " 1.25"
+"%5f" -% 0.75         == " 0.75"
+"%5f" -% (-Infinity)  == "-Infinity"
+"%5f" -% (0 % 1)      == "  0.0"
+"%5f" -% (1 % 1)      == "  1.0"
+"%5f" -% ((-1) % 1)   == " -1.0"
+"%5f" -% (1 % 2)      == "  0.5"
+"%5f" -% (2 % 1)      == "  2.0"
+"%5f" -% ((-1) % 2)   == " -0.5"
+"%5f" -% ((-2) % 1)   == " -2.0"
+"%5f" -% (1 % 3)      == "    0"
+"%5f" -% (3 % 1)      == "  3.0"
+"%5f" -% ((-1) % 3)   == "   -0"
+"%5f" -% ((-3) % 1)   == " -3.0"
+"%5f" -% (1 % 4)      == " 0.25"
+"%5f" -% 'a'          == "!!!!!"
+"%5f" -% ' '          == "!!!!!"
+"%5f" -% 'b'          == "!!!!!"
+"%5f" -% 'A'          == "!!!!!"
+"%5f" -% 'c'          == "!!!!!"
+"%5f" -% '\n'         == "!!!!!"
+"%5f" -% 'd'          == "!!!!!"
+"%5f" -% '0'          == "!!!!!"
+"%5f" -% 'e'          == "!!!!!"
+"%5f" -% 'B'          == "!!!!!"
+"%5f" -% 'f'          == "!!!!!"
+"%5f" -% '!'          == "!!!!!"
+"%5f" -% False        == "!!!!!"
+"%5f" -% True         == "!!!!!"
+
diff --git a/tests/diff/examples-6r.out b/tests/diff/examples-6r.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-6r.out
@@ -0,0 +1,63 @@
+"%6r" -% ""           == "    \"\""
+"%6r" -% "a"          == "   \"a\""
+"%6r" -% "aa"         == "  \"aa\""
+"%6r" -% " "          == "   \" \""
+"%6r" -% "aaa"        == " \"aaa\""
+"%6r" -% "a "         == "  \"a \""
+"%6r" -% " a"         == "  \" a\""
+"%6r" -% "b"          == "   \"b\""
+"%6r" -% "aaaa"       == "\"aaaa\""
+"%6r" -% "aa "        == " \"aa \""
+"%6r" -% "a a"        == " \"a a\""
+"%6r" -% "ab"         == "  \"ab\""
+"%6r" -% 0            == "     0"
+"%6r" -% 1            == "     1"
+"%6r" -% (-1)         == "    -1"
+"%6r" -% 2            == "     2"
+"%6r" -% (-2)         == "    -2"
+"%6r" -% 3            == "     3"
+"%6r" -% (-3)         == "    -3"
+"%6r" -% 4            == "     4"
+"%6r" -% (-4)         == "    -4"
+"%6r" -% 5            == "     5"
+"%6r" -% (-5)         == "    -5"
+"%6r" -% 6            == "     6"
+"%6r" -% 0.0          == "   0.0"
+"%6r" -% 1.0          == "   1.0"
+"%6r" -% 0.5          == "   0.5"
+"%6r" -% (-1.0)       == "  -1.0"
+"%6r" -% 1.5          == "   1.5"
+"%6r" -% 0.25         == "  0.25"
+"%6r" -% Infinity     == "Infinity"
+"%6r" -% 2.0          == "   2.0"
+"%6r" -% (-0.5)       == "  -0.5"
+"%6r" -% 1.25         == "  1.25"
+"%6r" -% 0.75         == "  0.75"
+"%6r" -% (-Infinity)  == "-Infinity"
+"%6r" -% (0 % 1)      == " 0 % 1"
+"%6r" -% (1 % 1)      == " 1 % 1"
+"%6r" -% ((-1) % 1)   == "(-1) % 1"
+"%6r" -% (1 % 2)      == " 1 % 2"
+"%6r" -% (2 % 1)      == " 2 % 1"
+"%6r" -% ((-1) % 2)   == "(-1) % 2"
+"%6r" -% ((-2) % 1)   == "(-2) % 1"
+"%6r" -% (1 % 3)      == " 1 % 3"
+"%6r" -% (3 % 1)      == " 3 % 1"
+"%6r" -% ((-1) % 3)   == "(-1) % 3"
+"%6r" -% ((-3) % 1)   == "(-3) % 1"
+"%6r" -% (1 % 4)      == " 1 % 4"
+"%6r" -% 'a'          == "   'a'"
+"%6r" -% ' '          == "   ' '"
+"%6r" -% 'b'          == "   'b'"
+"%6r" -% 'A'          == "   'A'"
+"%6r" -% 'c'          == "   'c'"
+"%6r" -% '\n'         == "  '\\n'"
+"%6r" -% 'd'          == "   'd'"
+"%6r" -% '0'          == "   '0'"
+"%6r" -% 'e'          == "   'e'"
+"%6r" -% 'B'          == "   'B'"
+"%6r" -% 'f'          == "   'f'"
+"%6r" -% '!'          == "   '!'"
+"%6r" -% False        == " False"
+"%6r" -% True         == "  True"
+
diff --git a/tests/diff/examples-6s.out b/tests/diff/examples-6s.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-6s.out
@@ -0,0 +1,63 @@
+"%6s" -% ""           == "      "
+"%6s" -% "a"          == "     a"
+"%6s" -% "aa"         == "    aa"
+"%6s" -% " "          == "      "
+"%6s" -% "aaa"        == "   aaa"
+"%6s" -% "a "         == "    a "
+"%6s" -% " a"         == "     a"
+"%6s" -% "b"          == "     b"
+"%6s" -% "aaaa"       == "  aaaa"
+"%6s" -% "aa "        == "   aa "
+"%6s" -% "a a"        == "   a a"
+"%6s" -% "ab"         == "    ab"
+"%6s" -% 0            == "     0"
+"%6s" -% 1            == "     1"
+"%6s" -% (-1)         == "    -1"
+"%6s" -% 2            == "     2"
+"%6s" -% (-2)         == "    -2"
+"%6s" -% 3            == "     3"
+"%6s" -% (-3)         == "    -3"
+"%6s" -% 4            == "     4"
+"%6s" -% (-4)         == "    -4"
+"%6s" -% 5            == "     5"
+"%6s" -% (-5)         == "    -5"
+"%6s" -% 6            == "     6"
+"%6s" -% 0.0          == "   0.0"
+"%6s" -% 1.0          == "   1.0"
+"%6s" -% 0.5          == "   0.5"
+"%6s" -% (-1.0)       == "  -1.0"
+"%6s" -% 1.5          == "   1.5"
+"%6s" -% 0.25         == "  0.25"
+"%6s" -% Infinity     == "Infinity"
+"%6s" -% 2.0          == "   2.0"
+"%6s" -% (-0.5)       == "  -0.5"
+"%6s" -% 1.25         == "  1.25"
+"%6s" -% 0.75         == "  0.75"
+"%6s" -% (-Infinity)  == "-Infinity"
+"%6s" -% (0 % 1)      == " 0 % 1"
+"%6s" -% (1 % 1)      == " 1 % 1"
+"%6s" -% ((-1) % 1)   == "(-1) % 1"
+"%6s" -% (1 % 2)      == " 1 % 2"
+"%6s" -% (2 % 1)      == " 2 % 1"
+"%6s" -% ((-1) % 2)   == "(-1) % 2"
+"%6s" -% ((-2) % 1)   == "(-2) % 1"
+"%6s" -% (1 % 3)      == " 1 % 3"
+"%6s" -% (3 % 1)      == " 3 % 1"
+"%6s" -% ((-1) % 3)   == "(-1) % 3"
+"%6s" -% ((-3) % 1)   == "(-3) % 1"
+"%6s" -% (1 % 4)      == " 1 % 4"
+"%6s" -% 'a'          == "   'a'"
+"%6s" -% ' '          == "   ' '"
+"%6s" -% 'b'          == "   'b'"
+"%6s" -% 'A'          == "   'A'"
+"%6s" -% 'c'          == "   'c'"
+"%6s" -% '\n'         == "  '\\n'"
+"%6s" -% 'd'          == "   'd'"
+"%6s" -% '0'          == "   '0'"
+"%6s" -% 'e'          == "   'e'"
+"%6s" -% 'B'          == "   'B'"
+"%6s" -% 'f'          == "   'f'"
+"%6s" -% '!'          == "   '!'"
+"%6s" -% False        == " False"
+"%6s" -% True         == "  True"
+
diff --git a/tests/diff/examples-X.out b/tests/diff/examples-X.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-X.out
@@ -0,0 +1,63 @@
+"%X" -% ""           == "!"
+"%X" -% "a"          == "!"
+"%X" -% "aa"         == "!"
+"%X" -% " "          == "!"
+"%X" -% "aaa"        == "!"
+"%X" -% "a "         == "!"
+"%X" -% " a"         == "!"
+"%X" -% "b"          == "!"
+"%X" -% "aaaa"       == "!"
+"%X" -% "aa "        == "!"
+"%X" -% "a a"        == "!"
+"%X" -% "ab"         == "!"
+"%X" -% 0            == "0"
+"%X" -% 1            == "1"
+"%X" -% (-1)         == "-1"
+"%X" -% 2            == "2"
+"%X" -% (-2)         == "-2"
+"%X" -% 3            == "3"
+"%X" -% (-3)         == "-3"
+"%X" -% 4            == "4"
+"%X" -% (-4)         == "-4"
+"%X" -% 5            == "5"
+"%X" -% (-5)         == "-5"
+"%X" -% 6            == "6"
+"%X" -% 0.0          == "0"
+"%X" -% 1.0          == "1"
+"%X" -% 0.5          == "0.8"
+"%X" -% (-1.0)       == "-1"
+"%X" -% 1.5          == "1.8"
+"%X" -% 0.25         == "0.4"
+"%X" -% Infinity     == "Infinity"
+"%X" -% 2.0          == "2"
+"%X" -% (-0.5)       == "-0.8"
+"%X" -% 1.25         == "1.4"
+"%X" -% 0.75         == "0.C"
+"%X" -% (-Infinity)  == "-Infinity"
+"%X" -% (0 % 1)      == "0"
+"%X" -% (1 % 1)      == "1"
+"%X" -% ((-1) % 1)   == "-1"
+"%X" -% (1 % 2)      == "0.8"
+"%X" -% (2 % 1)      == "2"
+"%X" -% ((-1) % 2)   == "-0.8"
+"%X" -% ((-2) % 1)   == "-2"
+"%X" -% (1 % 3)      == "0"
+"%X" -% (3 % 1)      == "3"
+"%X" -% ((-1) % 3)   == "-0"
+"%X" -% ((-3) % 1)   == "-3"
+"%X" -% (1 % 4)      == "0.4"
+"%X" -% 'a'          == "!"
+"%X" -% ' '          == "!"
+"%X" -% 'b'          == "!"
+"%X" -% 'A'          == "!"
+"%X" -% 'c'          == "!"
+"%X" -% '\n'         == "!"
+"%X" -% 'd'          == "!"
+"%X" -% '0'          == "!"
+"%X" -% 'e'          == "!"
+"%X" -% 'B'          == "!"
+"%X" -% 'f'          == "!"
+"%X" -% '!'          == "!"
+"%X" -% False        == "!"
+"%X" -% True         == "!"
+
diff --git a/tests/diff/examples-_3i.out b/tests/diff/examples-_3i.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-_3i.out
@@ -0,0 +1,63 @@
+"% 3i" -% ""           == "!!!"
+"% 3i" -% "a"          == "!!!"
+"% 3i" -% "aa"         == "!!!"
+"% 3i" -% " "          == "!!!"
+"% 3i" -% "aaa"        == "!!!"
+"% 3i" -% "a "         == "!!!"
+"% 3i" -% " a"         == "!!!"
+"% 3i" -% "b"          == "!!!"
+"% 3i" -% "aaaa"       == "!!!"
+"% 3i" -% "aa "        == "!!!"
+"% 3i" -% "a a"        == "!!!"
+"% 3i" -% "ab"         == "!!!"
+"% 3i" -% 0            == "  0"
+"% 3i" -% 1            == "  1"
+"% 3i" -% (-1)         == " -1"
+"% 3i" -% 2            == "  2"
+"% 3i" -% (-2)         == " -2"
+"% 3i" -% 3            == "  3"
+"% 3i" -% (-3)         == " -3"
+"% 3i" -% 4            == "  4"
+"% 3i" -% (-4)         == " -4"
+"% 3i" -% 5            == "  5"
+"% 3i" -% (-5)         == " -5"
+"% 3i" -% 6            == "  6"
+"% 3i" -% 0.0          == "  0"
+"% 3i" -% 1.0          == "  1"
+"% 3i" -% 0.5          == "  0"
+"% 3i" -% (-1.0)       == " -1"
+"% 3i" -% 1.5          == "  2"
+"% 3i" -% 0.25         == "  0"
+"% 3i" -% Infinity     == " Infinity"
+"% 3i" -% 2.0          == "  2"
+"% 3i" -% (-0.5)       == " -0"
+"% 3i" -% 1.25         == "  1"
+"% 3i" -% 0.75         == "  1"
+"% 3i" -% (-Infinity)  == "-Infinity"
+"% 3i" -% (0 % 1)      == "  0"
+"% 3i" -% (1 % 1)      == "  1"
+"% 3i" -% ((-1) % 1)   == " -1"
+"% 3i" -% (1 % 2)      == "  0"
+"% 3i" -% (2 % 1)      == "  2"
+"% 3i" -% ((-1) % 2)   == " -0"
+"% 3i" -% ((-2) % 1)   == " -2"
+"% 3i" -% (1 % 3)      == "  0"
+"% 3i" -% (3 % 1)      == "  3"
+"% 3i" -% ((-1) % 3)   == " -0"
+"% 3i" -% ((-3) % 1)   == " -3"
+"% 3i" -% (1 % 4)      == "  0"
+"% 3i" -% 'a'          == "!!!"
+"% 3i" -% ' '          == "!!!"
+"% 3i" -% 'b'          == "!!!"
+"% 3i" -% 'A'          == "!!!"
+"% 3i" -% 'c'          == "!!!"
+"% 3i" -% '\n'         == "!!!"
+"% 3i" -% 'd'          == "!!!"
+"% 3i" -% '0'          == "!!!"
+"% 3i" -% 'e'          == "!!!"
+"% 3i" -% 'B'          == "!!!"
+"% 3i" -% 'f'          == "!!!"
+"% 3i" -% '!'          == "!!!"
+"% 3i" -% False        == "!!!"
+"% 3i" -% True         == "!!!"
+
diff --git a/tests/diff/examples-abc.out b/tests/diff/examples-abc.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-abc.out
@@ -0,0 +1,63 @@
+"abc" -% ""           == "abc"
+"abc" -% "a"          == "abc"
+"abc" -% "aa"         == "abc"
+"abc" -% " "          == "abc"
+"abc" -% "aaa"        == "abc"
+"abc" -% "a "         == "abc"
+"abc" -% " a"         == "abc"
+"abc" -% "b"          == "abc"
+"abc" -% "aaaa"       == "abc"
+"abc" -% "aa "        == "abc"
+"abc" -% "a a"        == "abc"
+"abc" -% "ab"         == "abc"
+"abc" -% 0            == "abc"
+"abc" -% 1            == "abc"
+"abc" -% (-1)         == "abc"
+"abc" -% 2            == "abc"
+"abc" -% (-2)         == "abc"
+"abc" -% 3            == "abc"
+"abc" -% (-3)         == "abc"
+"abc" -% 4            == "abc"
+"abc" -% (-4)         == "abc"
+"abc" -% 5            == "abc"
+"abc" -% (-5)         == "abc"
+"abc" -% 6            == "abc"
+"abc" -% 0.0          == "abc"
+"abc" -% 1.0          == "abc"
+"abc" -% 0.5          == "abc"
+"abc" -% (-1.0)       == "abc"
+"abc" -% 1.5          == "abc"
+"abc" -% 0.25         == "abc"
+"abc" -% Infinity     == "abc"
+"abc" -% 2.0          == "abc"
+"abc" -% (-0.5)       == "abc"
+"abc" -% 1.25         == "abc"
+"abc" -% 0.75         == "abc"
+"abc" -% (-Infinity)  == "abc"
+"abc" -% (0 % 1)      == "abc"
+"abc" -% (1 % 1)      == "abc"
+"abc" -% ((-1) % 1)   == "abc"
+"abc" -% (1 % 2)      == "abc"
+"abc" -% (2 % 1)      == "abc"
+"abc" -% ((-1) % 2)   == "abc"
+"abc" -% ((-2) % 1)   == "abc"
+"abc" -% (1 % 3)      == "abc"
+"abc" -% (3 % 1)      == "abc"
+"abc" -% ((-1) % 3)   == "abc"
+"abc" -% ((-3) % 1)   == "abc"
+"abc" -% (1 % 4)      == "abc"
+"abc" -% 'a'          == "abc"
+"abc" -% ' '          == "abc"
+"abc" -% 'b'          == "abc"
+"abc" -% 'A'          == "abc"
+"abc" -% 'c'          == "abc"
+"abc" -% '\n'         == "abc"
+"abc" -% 'd'          == "abc"
+"abc" -% '0'          == "abc"
+"abc" -% 'e'          == "abc"
+"abc" -% 'B'          == "abc"
+"abc" -% 'f'          == "abc"
+"abc" -% '!'          == "abc"
+"abc" -% False        == "abc"
+"abc" -% True         == "abc"
+
diff --git a/tests/diff/examples-b.out b/tests/diff/examples-b.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-b.out
@@ -0,0 +1,63 @@
+"%b" -% ""           == "!"
+"%b" -% "a"          == "!"
+"%b" -% "aa"         == "!"
+"%b" -% " "          == "!"
+"%b" -% "aaa"        == "!"
+"%b" -% "a "         == "!"
+"%b" -% " a"         == "!"
+"%b" -% "b"          == "!"
+"%b" -% "aaaa"       == "!"
+"%b" -% "aa "        == "!"
+"%b" -% "a a"        == "!"
+"%b" -% "ab"         == "!"
+"%b" -% 0            == "0"
+"%b" -% 1            == "1"
+"%b" -% (-1)         == "-1"
+"%b" -% 2            == "10"
+"%b" -% (-2)         == "-10"
+"%b" -% 3            == "11"
+"%b" -% (-3)         == "-11"
+"%b" -% 4            == "100"
+"%b" -% (-4)         == "-100"
+"%b" -% 5            == "101"
+"%b" -% (-5)         == "-101"
+"%b" -% 6            == "110"
+"%b" -% 0.0          == "0"
+"%b" -% 1.0          == "1"
+"%b" -% 0.5          == "0.1"
+"%b" -% (-1.0)       == "-1"
+"%b" -% 1.5          == "1.1"
+"%b" -% 0.25         == "0.01"
+"%b" -% Infinity     == "Infinity"
+"%b" -% 2.0          == "10"
+"%b" -% (-0.5)       == "-0.1"
+"%b" -% 1.25         == "1.01"
+"%b" -% 0.75         == "0.11"
+"%b" -% (-Infinity)  == "-Infinity"
+"%b" -% (0 % 1)      == "0"
+"%b" -% (1 % 1)      == "1"
+"%b" -% ((-1) % 1)   == "-1"
+"%b" -% (1 % 2)      == "0.1"
+"%b" -% (2 % 1)      == "10"
+"%b" -% ((-1) % 2)   == "-0.1"
+"%b" -% ((-2) % 1)   == "-10"
+"%b" -% (1 % 3)      == "0"
+"%b" -% (3 % 1)      == "11"
+"%b" -% ((-1) % 3)   == "-0"
+"%b" -% ((-3) % 1)   == "-11"
+"%b" -% (1 % 4)      == "0.01"
+"%b" -% 'a'          == "!"
+"%b" -% ' '          == "!"
+"%b" -% 'b'          == "!"
+"%b" -% 'A'          == "!"
+"%b" -% 'c'          == "!"
+"%b" -% '\n'         == "!"
+"%b" -% 'd'          == "!"
+"%b" -% '0'          == "!"
+"%b" -% 'e'          == "!"
+"%b" -% 'B'          == "!"
+"%b" -% 'f'          == "!"
+"%b" -% '!'          == "!"
+"%b" -% False        == "!"
+"%b" -% True         == "!"
+
diff --git a/tests/diff/examples-c.out b/tests/diff/examples-c.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-c.out
@@ -0,0 +1,63 @@
+"%c" -% ""           == "!"
+"%c" -% "a"          == "!"
+"%c" -% "aa"         == "!"
+"%c" -% " "          == "!"
+"%c" -% "aaa"        == "!"
+"%c" -% "a "         == "!"
+"%c" -% " a"         == "!"
+"%c" -% "b"          == "!"
+"%c" -% "aaaa"       == "!"
+"%c" -% "aa "        == "!"
+"%c" -% "a a"        == "!"
+"%c" -% "ab"         == "!"
+"%c" -% 0            == "!"
+"%c" -% 1            == "!"
+"%c" -% (-1)         == "!"
+"%c" -% 2            == "!"
+"%c" -% (-2)         == "!"
+"%c" -% 3            == "!"
+"%c" -% (-3)         == "!"
+"%c" -% 4            == "!"
+"%c" -% (-4)         == "!"
+"%c" -% 5            == "!"
+"%c" -% (-5)         == "!"
+"%c" -% 6            == "!"
+"%c" -% 0.0          == "!"
+"%c" -% 1.0          == "!"
+"%c" -% 0.5          == "!"
+"%c" -% (-1.0)       == "!"
+"%c" -% 1.5          == "!"
+"%c" -% 0.25         == "!"
+"%c" -% Infinity     == "!"
+"%c" -% 2.0          == "!"
+"%c" -% (-0.5)       == "!"
+"%c" -% 1.25         == "!"
+"%c" -% 0.75         == "!"
+"%c" -% (-Infinity)  == "!"
+"%c" -% (0 % 1)      == "!"
+"%c" -% (1 % 1)      == "!"
+"%c" -% ((-1) % 1)   == "!"
+"%c" -% (1 % 2)      == "!"
+"%c" -% (2 % 1)      == "!"
+"%c" -% ((-1) % 2)   == "!"
+"%c" -% ((-2) % 1)   == "!"
+"%c" -% (1 % 3)      == "!"
+"%c" -% (3 % 1)      == "!"
+"%c" -% ((-1) % 3)   == "!"
+"%c" -% ((-3) % 1)   == "!"
+"%c" -% (1 % 4)      == "!"
+"%c" -% 'a'          == "a"
+"%c" -% ' '          == " "
+"%c" -% 'b'          == "b"
+"%c" -% 'A'          == "A"
+"%c" -% 'c'          == "c"
+"%c" -% '\n'         == "\n"
+"%c" -% 'd'          == "d"
+"%c" -% '0'          == "0"
+"%c" -% 'e'          == "e"
+"%c" -% 'B'          == "B"
+"%c" -% 'f'          == "f"
+"%c" -% '!'          == "!"
+"%c" -% False        == "!"
+"%c" -% True         == "!"
+
diff --git a/tests/diff/examples-f.out b/tests/diff/examples-f.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-f.out
@@ -0,0 +1,63 @@
+"%f" -% ""           == "!"
+"%f" -% "a"          == "!"
+"%f" -% "aa"         == "!"
+"%f" -% " "          == "!"
+"%f" -% "aaa"        == "!"
+"%f" -% "a "         == "!"
+"%f" -% " a"         == "!"
+"%f" -% "b"          == "!"
+"%f" -% "aaaa"       == "!"
+"%f" -% "aa "        == "!"
+"%f" -% "a a"        == "!"
+"%f" -% "ab"         == "!"
+"%f" -% 0            == "0.0"
+"%f" -% 1            == "1.0"
+"%f" -% (-1)         == "-1.0"
+"%f" -% 2            == "2.0"
+"%f" -% (-2)         == "-2.0"
+"%f" -% 3            == "3.0"
+"%f" -% (-3)         == "-3.0"
+"%f" -% 4            == "4.0"
+"%f" -% (-4)         == "-4.0"
+"%f" -% 5            == "5.0"
+"%f" -% (-5)         == "-5.0"
+"%f" -% 6            == "6.0"
+"%f" -% 0.0          == "0.0"
+"%f" -% 1.0          == "1.0"
+"%f" -% 0.5          == "0.5"
+"%f" -% (-1.0)       == "-1.0"
+"%f" -% 1.5          == "1.5"
+"%f" -% 0.25         == "0.25"
+"%f" -% Infinity     == "Infinity"
+"%f" -% 2.0          == "2.0"
+"%f" -% (-0.5)       == "-0.5"
+"%f" -% 1.25         == "1.25"
+"%f" -% 0.75         == "0.75"
+"%f" -% (-Infinity)  == "-Infinity"
+"%f" -% (0 % 1)      == "0.0"
+"%f" -% (1 % 1)      == "1.0"
+"%f" -% ((-1) % 1)   == "-1.0"
+"%f" -% (1 % 2)      == "0.5"
+"%f" -% (2 % 1)      == "2.0"
+"%f" -% ((-1) % 2)   == "-0.5"
+"%f" -% ((-2) % 1)   == "-2.0"
+"%f" -% (1 % 3)      == "0"
+"%f" -% (3 % 1)      == "3.0"
+"%f" -% ((-1) % 3)   == "-0"
+"%f" -% ((-3) % 1)   == "-3.0"
+"%f" -% (1 % 4)      == "0.25"
+"%f" -% 'a'          == "!"
+"%f" -% ' '          == "!"
+"%f" -% 'b'          == "!"
+"%f" -% 'A'          == "!"
+"%f" -% 'c'          == "!"
+"%f" -% '\n'         == "!"
+"%f" -% 'd'          == "!"
+"%f" -% '0'          == "!"
+"%f" -% 'e'          == "!"
+"%f" -% 'B'          == "!"
+"%f" -% 'f'          == "!"
+"%f" -% '!'          == "!"
+"%f" -% False        == "!"
+"%f" -% True         == "!"
+
diff --git a/tests/diff/examples-i.out b/tests/diff/examples-i.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-i.out
@@ -0,0 +1,63 @@
+"%i" -% ""           == "!"
+"%i" -% "a"          == "!"
+"%i" -% "aa"         == "!"
+"%i" -% " "          == "!"
+"%i" -% "aaa"        == "!"
+"%i" -% "a "         == "!"
+"%i" -% " a"         == "!"
+"%i" -% "b"          == "!"
+"%i" -% "aaaa"       == "!"
+"%i" -% "aa "        == "!"
+"%i" -% "a a"        == "!"
+"%i" -% "ab"         == "!"
+"%i" -% 0            == "0"
+"%i" -% 1            == "1"
+"%i" -% (-1)         == "-1"
+"%i" -% 2            == "2"
+"%i" -% (-2)         == "-2"
+"%i" -% 3            == "3"
+"%i" -% (-3)         == "-3"
+"%i" -% 4            == "4"
+"%i" -% (-4)         == "-4"
+"%i" -% 5            == "5"
+"%i" -% (-5)         == "-5"
+"%i" -% 6            == "6"
+"%i" -% 0.0          == "0"
+"%i" -% 1.0          == "1"
+"%i" -% 0.5          == "0"
+"%i" -% (-1.0)       == "-1"
+"%i" -% 1.5          == "2"
+"%i" -% 0.25         == "0"
+"%i" -% Infinity     == "Infinity"
+"%i" -% 2.0          == "2"
+"%i" -% (-0.5)       == "-0"
+"%i" -% 1.25         == "1"
+"%i" -% 0.75         == "1"
+"%i" -% (-Infinity)  == "-Infinity"
+"%i" -% (0 % 1)      == "0"
+"%i" -% (1 % 1)      == "1"
+"%i" -% ((-1) % 1)   == "-1"
+"%i" -% (1 % 2)      == "0"
+"%i" -% (2 % 1)      == "2"
+"%i" -% ((-1) % 2)   == "-0"
+"%i" -% ((-2) % 1)   == "-2"
+"%i" -% (1 % 3)      == "0"
+"%i" -% (3 % 1)      == "3"
+"%i" -% ((-1) % 3)   == "-0"
+"%i" -% ((-3) % 1)   == "-3"
+"%i" -% (1 % 4)      == "0"
+"%i" -% 'a'          == "!"
+"%i" -% ' '          == "!"
+"%i" -% 'b'          == "!"
+"%i" -% 'A'          == "!"
+"%i" -% 'c'          == "!"
+"%i" -% '\n'         == "!"
+"%i" -% 'd'          == "!"
+"%i" -% '0'          == "!"
+"%i" -% 'e'          == "!"
+"%i" -% 'B'          == "!"
+"%i" -% 'f'          == "!"
+"%i" -% '!'          == "!"
+"%i" -% False        == "!"
+"%i" -% True         == "!"
+
diff --git a/tests/diff/examples-o.out b/tests/diff/examples-o.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-o.out
@@ -0,0 +1,63 @@
+"%o" -% ""           == "!"
+"%o" -% "a"          == "!"
+"%o" -% "aa"         == "!"
+"%o" -% " "          == "!"
+"%o" -% "aaa"        == "!"
+"%o" -% "a "         == "!"
+"%o" -% " a"         == "!"
+"%o" -% "b"          == "!"
+"%o" -% "aaaa"       == "!"
+"%o" -% "aa "        == "!"
+"%o" -% "a a"        == "!"
+"%o" -% "ab"         == "!"
+"%o" -% 0            == "0"
+"%o" -% 1            == "1"
+"%o" -% (-1)         == "-1"
+"%o" -% 2            == "2"
+"%o" -% (-2)         == "-2"
+"%o" -% 3            == "3"
+"%o" -% (-3)         == "-3"
+"%o" -% 4            == "4"
+"%o" -% (-4)         == "-4"
+"%o" -% 5            == "5"
+"%o" -% (-5)         == "-5"
+"%o" -% 6            == "6"
+"%o" -% 0.0          == "0"
+"%o" -% 1.0          == "1"
+"%o" -% 0.5          == "0.4"
+"%o" -% (-1.0)       == "-1"
+"%o" -% 1.5          == "1.4"
+"%o" -% 0.25         == "0.2"
+"%o" -% Infinity     == "Infinity"
+"%o" -% 2.0          == "2"
+"%o" -% (-0.5)       == "-0.4"
+"%o" -% 1.25         == "1.2"
+"%o" -% 0.75         == "0.6"
+"%o" -% (-Infinity)  == "-Infinity"
+"%o" -% (0 % 1)      == "0"
+"%o" -% (1 % 1)      == "1"
+"%o" -% ((-1) % 1)   == "-1"
+"%o" -% (1 % 2)      == "0.4"
+"%o" -% (2 % 1)      == "2"
+"%o" -% ((-1) % 2)   == "-0.4"
+"%o" -% ((-2) % 1)   == "-2"
+"%o" -% (1 % 3)      == "0"
+"%o" -% (3 % 1)      == "3"
+"%o" -% ((-1) % 3)   == "-0"
+"%o" -% ((-3) % 1)   == "-3"
+"%o" -% (1 % 4)      == "0.2"
+"%o" -% 'a'          == "!"
+"%o" -% ' '          == "!"
+"%o" -% 'b'          == "!"
+"%o" -% 'A'          == "!"
+"%o" -% 'c'          == "!"
+"%o" -% '\n'         == "!"
+"%o" -% 'd'          == "!"
+"%o" -% '0'          == "!"
+"%o" -% 'e'          == "!"
+"%o" -% 'B'          == "!"
+"%o" -% 'f'          == "!"
+"%o" -% '!'          == "!"
+"%o" -% False        == "!"
+"%o" -% True         == "!"
+
diff --git a/tests/diff/examples-r.out b/tests/diff/examples-r.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-r.out
@@ -0,0 +1,63 @@
+"%r" -% ""           == "\"\""
+"%r" -% "a"          == "\"a\""
+"%r" -% "aa"         == "\"aa\""
+"%r" -% " "          == "\" \""
+"%r" -% "aaa"        == "\"aaa\""
+"%r" -% "a "         == "\"a \""
+"%r" -% " a"         == "\" a\""
+"%r" -% "b"          == "\"b\""
+"%r" -% "aaaa"       == "\"aaaa\""
+"%r" -% "aa "        == "\"aa \""
+"%r" -% "a a"        == "\"a a\""
+"%r" -% "ab"         == "\"ab\""
+"%r" -% 0            == "0"
+"%r" -% 1            == "1"
+"%r" -% (-1)         == "-1"
+"%r" -% 2            == "2"
+"%r" -% (-2)         == "-2"
+"%r" -% 3            == "3"
+"%r" -% (-3)         == "-3"
+"%r" -% 4            == "4"
+"%r" -% (-4)         == "-4"
+"%r" -% 5            == "5"
+"%r" -% (-5)         == "-5"
+"%r" -% 6            == "6"
+"%r" -% 0.0          == "0.0"
+"%r" -% 1.0          == "1.0"
+"%r" -% 0.5          == "0.5"
+"%r" -% (-1.0)       == "-1.0"
+"%r" -% 1.5          == "1.5"
+"%r" -% 0.25         == "0.25"
+"%r" -% Infinity     == "Infinity"
+"%r" -% 2.0          == "2.0"
+"%r" -% (-0.5)       == "-0.5"
+"%r" -% 1.25         == "1.25"
+"%r" -% 0.75         == "0.75"
+"%r" -% (-Infinity)  == "-Infinity"
+"%r" -% (0 % 1)      == "0 % 1"
+"%r" -% (1 % 1)      == "1 % 1"
+"%r" -% ((-1) % 1)   == "(-1) % 1"
+"%r" -% (1 % 2)      == "1 % 2"
+"%r" -% (2 % 1)      == "2 % 1"
+"%r" -% ((-1) % 2)   == "(-1) % 2"
+"%r" -% ((-2) % 1)   == "(-2) % 1"
+"%r" -% (1 % 3)      == "1 % 3"
+"%r" -% (3 % 1)      == "3 % 1"
+"%r" -% ((-1) % 3)   == "(-1) % 3"
+"%r" -% ((-3) % 1)   == "(-3) % 1"
+"%r" -% (1 % 4)      == "1 % 4"
+"%r" -% 'a'          == "'a'"
+"%r" -% ' '          == "' '"
+"%r" -% 'b'          == "'b'"
+"%r" -% 'A'          == "'A'"
+"%r" -% 'c'          == "'c'"
+"%r" -% '\n'         == "'\\n'"
+"%r" -% 'd'          == "'d'"
+"%r" -% '0'          == "'0'"
+"%r" -% 'e'          == "'e'"
+"%r" -% 'B'          == "'B'"
+"%r" -% 'f'          == "'f'"
+"%r" -% '!'          == "'!'"
+"%r" -% False        == "False"
+"%r" -% True         == "True"
+
diff --git a/tests/diff/examples-s.out b/tests/diff/examples-s.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-s.out
@@ -0,0 +1,63 @@
+"%s" -% ""           == ""
+"%s" -% "a"          == "a"
+"%s" -% "aa"         == "aa"
+"%s" -% " "          == " "
+"%s" -% "aaa"        == "aaa"
+"%s" -% "a "         == "a "
+"%s" -% " a"         == " a"
+"%s" -% "b"          == "b"
+"%s" -% "aaaa"       == "aaaa"
+"%s" -% "aa "        == "aa "
+"%s" -% "a a"        == "a a"
+"%s" -% "ab"         == "ab"
+"%s" -% 0            == "0"
+"%s" -% 1            == "1"
+"%s" -% (-1)         == "-1"
+"%s" -% 2            == "2"
+"%s" -% (-2)         == "-2"
+"%s" -% 3            == "3"
+"%s" -% (-3)         == "-3"
+"%s" -% 4            == "4"
+"%s" -% (-4)         == "-4"
+"%s" -% 5            == "5"
+"%s" -% (-5)         == "-5"
+"%s" -% 6            == "6"
+"%s" -% 0.0          == "0.0"
+"%s" -% 1.0          == "1.0"
+"%s" -% 0.5          == "0.5"
+"%s" -% (-1.0)       == "-1.0"
+"%s" -% 1.5          == "1.5"
+"%s" -% 0.25         == "0.25"
+"%s" -% Infinity     == "Infinity"
+"%s" -% 2.0          == "2.0"
+"%s" -% (-0.5)       == "-0.5"
+"%s" -% 1.25         == "1.25"
+"%s" -% 0.75         == "0.75"
+"%s" -% (-Infinity)  == "-Infinity"
+"%s" -% (0 % 1)      == "0 % 1"
+"%s" -% (1 % 1)      == "1 % 1"
+"%s" -% ((-1) % 1)   == "(-1) % 1"
+"%s" -% (1 % 2)      == "1 % 2"
+"%s" -% (2 % 1)      == "2 % 1"
+"%s" -% ((-1) % 2)   == "(-1) % 2"
+"%s" -% ((-2) % 1)   == "(-2) % 1"
+"%s" -% (1 % 3)      == "1 % 3"
+"%s" -% (3 % 1)      == "3 % 1"
+"%s" -% ((-1) % 3)   == "(-1) % 3"
+"%s" -% ((-3) % 1)   == "(-3) % 1"
+"%s" -% (1 % 4)      == "1 % 4"
+"%s" -% 'a'          == "'a'"
+"%s" -% ' '          == "' '"
+"%s" -% 'b'          == "'b'"
+"%s" -% 'A'          == "'A'"
+"%s" -% 'c'          == "'c'"
+"%s" -% '\n'         == "'\\n'"
+"%s" -% 'd'          == "'d'"
+"%s" -% '0'          == "'0'"
+"%s" -% 'e'          == "'e'"
+"%s" -% 'B'          == "'B'"
+"%s" -% 'f'          == "'f'"
+"%s" -% '!'          == "'!'"
+"%s" -% False        == "False"
+"%s" -% True         == "True"
+
diff --git a/tests/diff/examples-x.out b/tests/diff/examples-x.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples-x.out
@@ -0,0 +1,63 @@
+"%x" -% ""           == "!"
+"%x" -% "a"          == "!"
+"%x" -% "aa"         == "!"
+"%x" -% " "          == "!"
+"%x" -% "aaa"        == "!"
+"%x" -% "a "         == "!"
+"%x" -% " a"         == "!"
+"%x" -% "b"          == "!"
+"%x" -% "aaaa"       == "!"
+"%x" -% "aa "        == "!"
+"%x" -% "a a"        == "!"
+"%x" -% "ab"         == "!"
+"%x" -% 0            == "0"
+"%x" -% 1            == "1"
+"%x" -% (-1)         == "-1"
+"%x" -% 2            == "2"
+"%x" -% (-2)         == "-2"
+"%x" -% 3            == "3"
+"%x" -% (-3)         == "-3"
+"%x" -% 4            == "4"
+"%x" -% (-4)         == "-4"
+"%x" -% 5            == "5"
+"%x" -% (-5)         == "-5"
+"%x" -% 6            == "6"
+"%x" -% 0.0          == "0"
+"%x" -% 1.0          == "1"
+"%x" -% 0.5          == "0.8"
+"%x" -% (-1.0)       == "-1"
+"%x" -% 1.5          == "1.8"
+"%x" -% 0.25         == "0.4"
+"%x" -% Infinity     == "Infinity"
+"%x" -% 2.0          == "2"
+"%x" -% (-0.5)       == "-0.8"
+"%x" -% 1.25         == "1.4"
+"%x" -% 0.75         == "0.c"
+"%x" -% (-Infinity)  == "-Infinity"
+"%x" -% (0 % 1)      == "0"
+"%x" -% (1 % 1)      == "1"
+"%x" -% ((-1) % 1)   == "-1"
+"%x" -% (1 % 2)      == "0.8"
+"%x" -% (2 % 1)      == "2"
+"%x" -% ((-1) % 2)   == "-0.8"
+"%x" -% ((-2) % 1)   == "-2"
+"%x" -% (1 % 3)      == "0"
+"%x" -% (3 % 1)      == "3"
+"%x" -% ((-1) % 3)   == "-0"
+"%x" -% ((-3) % 1)   == "-3"
+"%x" -% (1 % 4)      == "0.4"
+"%x" -% 'a'          == "!"
+"%x" -% ' '          == "!"
+"%x" -% 'b'          == "!"
+"%x" -% 'A'          == "!"
+"%x" -% 'c'          == "!"
+"%x" -% '\n'         == "!"
+"%x" -% 'd'          == "!"
+"%x" -% '0'          == "!"
+"%x" -% 'e'          == "!"
+"%x" -% 'B'          == "!"
+"%x" -% 'f'          == "!"
+"%x" -% '!'          == "!"
+"%x" -% False        == "!"
+"%x" -% True         == "!"
+
diff --git a/tests/diff/examples.out b/tests/diff/examples.out
new file mode 100644
--- /dev/null
+++ b/tests/diff/examples.out
@@ -0,0 +1,63 @@
+"%r" -% ""           == "\"\""
+"%r" -% "a"          == "\"a\""
+"%r" -% "aa"         == "\"aa\""
+"%r" -% " "          == "\" \""
+"%r" -% "aaa"        == "\"aaa\""
+"%r" -% "a "         == "\"a \""
+"%r" -% " a"         == "\" a\""
+"%r" -% "b"          == "\"b\""
+"%r" -% "aaaa"       == "\"aaaa\""
+"%r" -% "aa "        == "\"aa \""
+"%r" -% "a a"        == "\"a a\""
+"%r" -% "ab"         == "\"ab\""
+"%r" -% 0            == "0"
+"%r" -% 1            == "1"
+"%r" -% (-1)         == "-1"
+"%r" -% 2            == "2"
+"%r" -% (-2)         == "-2"
+"%r" -% 3            == "3"
+"%r" -% (-3)         == "-3"
+"%r" -% 4            == "4"
+"%r" -% (-4)         == "-4"
+"%r" -% 5            == "5"
+"%r" -% (-5)         == "-5"
+"%r" -% 6            == "6"
+"%r" -% 0.0          == "0.0"
+"%r" -% 1.0          == "1.0"
+"%r" -% 0.5          == "0.5"
+"%r" -% (-1.0)       == "-1.0"
+"%r" -% 1.5          == "1.5"
+"%r" -% 0.25         == "0.25"
+"%r" -% Infinity     == "Infinity"
+"%r" -% 2.0          == "2.0"
+"%r" -% (-0.5)       == "-0.5"
+"%r" -% 1.25         == "1.25"
+"%r" -% 0.75         == "0.75"
+"%r" -% (-Infinity)  == "-Infinity"
+"%r" -% (0 % 1)      == "0 % 1"
+"%r" -% (1 % 1)      == "1 % 1"
+"%r" -% ((-1) % 1)   == "(-1) % 1"
+"%r" -% (1 % 2)      == "1 % 2"
+"%r" -% (2 % 1)      == "2 % 1"
+"%r" -% ((-1) % 2)   == "(-1) % 2"
+"%r" -% ((-2) % 1)   == "(-2) % 1"
+"%r" -% (1 % 3)      == "1 % 3"
+"%r" -% (3 % 1)      == "3 % 1"
+"%r" -% ((-1) % 3)   == "(-1) % 3"
+"%r" -% ((-3) % 1)   == "(-3) % 1"
+"%r" -% (1 % 4)      == "1 % 4"
+"%r" -% 'a'          == "'a'"
+"%r" -% ' '          == "' '"
+"%r" -% 'b'          == "'b'"
+"%r" -% 'A'          == "'A'"
+"%r" -% 'c'          == "'c'"
+"%r" -% '\n'         == "'\\n'"
+"%r" -% 'd'          == "'d'"
+"%r" -% '0'          == "'0'"
+"%r" -% 'e'          == "'e'"
+"%r" -% 'B'          == "'B'"
+"%r" -% 'f'          == "'f'"
+"%r" -% '!'          == "'!'"
+"%r" -% False        == "False"
+"%r" -% True         == "True"
+
diff --git a/tests/test-number.hs b/tests/test-number.hs
new file mode 100644
--- /dev/null
+++ b/tests/test-number.hs
@@ -0,0 +1,186 @@
+-- Copyright (c) 2018 Rudy Matela.
+-- Distributed under the 3-Clause BSD licence (see the file LICENSE).
+import Test
+
+import Text.PercentFormat
+import Text.PercentFormat.Quotient (Quotient)
+import Data.List (isInfixOf)
+
+import qualified Data.Ratio as R
+
+main :: IO ()
+main = reportTests tests
+
+tests :: [Bool]
+tests =
+  [ True
+
+  -- single digits, positives
+  , "%i" % 1 == "1"
+  , "%d" % 2 == "2"
+  , "%x" % 3 == "3"
+  , "%o" % 4 == "4"
+  , "%b" % 5 == "101"
+  , "%f" % 6 == "6.0"
+
+  -- double digits, positives
+  , "%i" % 12 == "12"
+  , "%d" % 23 == "23"
+  , "%x" % 34 == "22"
+  , "%o" % 45 == "55"
+  , "%b" % 56 == "111000"
+  , "%f" % 67 == "67.0"
+
+  -- single digits, negatives
+  , "%i" % (-1) == "-1"
+  , "%d" % (-2) == "-2"
+  , "%x" % (-3) == "-3"
+  , "%o" % (-4) == "-4"
+  , "%b" % (-5) == "-101"
+  , "%f" % (-6) == "-6.0"
+
+  -- double digits, negatives
+  , "%i" % (-12) == "-12"
+  , "%d" % (-23) == "-23"
+  , "%x" % (-34) == "-22"
+  , "%o" % (-45) == "-55"
+  , "%b" % (-56) == "-111000"
+  , "%f" % (-67) == "-67.0"
+
+  -- Infinity
+  , "%f" % (1.0 / 0.0) == "Infinity"
+  , "%i" % (1.0 / 0.0) == "Infinity"
+  , "%d" % (1.0 / 0.0) == "Infinity"
+  , "%x" % (1.0 / 0.0) == "Infinity"
+  , "%o" % (1.0 / 0.0) == "Infinity"
+  , and $ [ "%%%i%c" -%% (n,c) -% (1.0 / 0.0) == "Infinity"
+          | n <- [-7..7], c <- "idxobf" ]
+
+  -- -Infinity
+  , "%f" % (-1.0 / 0.0) == "-Infinity"
+  , "%i" % (-1.0 / 0.0) == "-Infinity"
+  , "%d" % (-1.0 / 0.0) == "-Infinity"
+  , "%x" % (-1.0 / 0.0) == "-Infinity"
+  , "%o" % (-1.0 / 0.0) == "-Infinity"
+
+  -- NaN
+  , "%f" % (0.0 / 0.0) == "NaN"
+  , "%i" % (0.0 / 0.0) == "NaN"
+  , "%d" % (0.0 / 0.0) == "NaN"
+  , "%x" % (0.0 / 0.0) == "NaN"
+  , "%o" % (0.0 / 0.0) == "NaN"
+  , and $ [ "NaN" `isInfixOf` ("%%%i%c" -%% (n,c) -% (0.0 / 0.0))
+          | n <- [-7..7], c <- "idxobf" ]
+
+  -- decimal fractional digits
+  , "%f" % 1 == "1.0"
+  , "%f" % 1.1 == "1.1"
+  , "%f" % (10.0 / 3.0) == show (10.0 / 3.0)
+  , "%.3f" % (10.0 / 3.0) == "3.333"
+  , "%1.2f" % 1.1 == "1.10"
+  , "%06.3f" % 1.23 == "01.230"
+  , "%.1f" % 1.14 == "1.1"
+  , "%.3f" % (-1.2) == "-1.200"
+  , "%.1f" % 1.16 == "1.2"
+  , "%.1f" % 1.15 == "1.2"  -- round to the nearest even number
+  , "%.1f" % 1.25 == "1.2"  -- round to the nearest even number
+  , "%15.7f" % 1234567.1234567 == "1234567.1234567"
+  , "%17.8f" % (12345678.12345678 :: Quotient) == "12345678.12345678"
+--, "%17.8f" % 12345678.12345678 == "12345678.12345678"  -- GHC
+--, "%17.8f" % 12345678.12345678 == "12345678.12345680"  -- Hugs
+
+  -- fractional digits in other bases
+  , "%.2x" % (1.0 / 200.0) == "0.01"
+  , "%.2x" % (3.0 / 200.0) == "0.04"
+  , "%.8b" % (1.0 / 200.0) == "0.00000001"
+  , "%.8b" % (3.0 / 200.0) == "0.00000100"
+  , "%.1x" % (3.0 / 2.0) == "1.8"
+  , "%.1b" % (3.0 / 2.0) == "1.1"
+  , "%.0b" % (3.0 / 2.0) == "10"
+  , "%.0b" % (1.0 / 2.0) == "0"
+
+  -- %i rounds to the nearest integer, unless precision is specified
+  , "%i"   % 3.1 == "3"
+  , "%.*i" % 3.1 == "3.1"
+  , "%.2i" % 3.1 == "3.10"
+  , "%.*i" % 3.141 == "3.141"
+  , "%.2i" % 3.141 == "3.14"
+
+  -- %f shows a minimum of one fractional digit
+  , "%f" % 6 == "6.0"
+  , "%f" % 12 == "12.0"
+
+  -- positive/negative signal placement
+  , "%+i" % 1 == "+1"
+  , "% i" % 1 == " 1"
+  , "%+f" % 1.2 == "+1.2"
+  , "% f" % 1.2 == " 1.2"
+  , "%3d" % (-1) == " -1"
+  , "%4d" % (-1) == "  -1"
+  , "%03d" % (-1) == "-01"
+  , "%04d" % (-1) == "-001"
+  , "%+3d" % 1 == " +1"
+  , "%+4d" % 1 == "  +1"
+  , "%+03d" % 1 == "+01"
+  , "%+04d" % 1 == "+001"
+  , "% 07.3f" %   3.3  == " 03.300"
+  , "% 07.3f" % (-3.3) == "-03.300"
+  , "% 7.3f"  %   3.3  == "  3.300"
+  , "% 7.3f"  % (-3.3) == " -3.300"
+
+  , "%07.3d" -% 10.5 == "010.500"
+  , "%07.3x" -% 10.5 == "00a.800"
+  , "%07.3b" -% 10.5 == "1010.100"
+
+  -- Ratios
+  , "asdf %r qwer" -% (10 R.% 3) == "asdf 10 % 3 qwer"
+  , "asdf %07.3f qwer" -% (10 R.% 3) == "asdf 003.333 qwer"
+
+  , "%x" -% 0xfedcba9876543210 == "fedcba9876543210"
+  , "%X" -% 0xFEDCBA9876543210 == "FEDCBA9876543210"
+
+  , "%i"    % pi == "3"
+  , "%.8d"  % pi == "3.14159265"
+--, "%d"    % pi == "3.14159265358979"   -- Hugs
+--, "%d"    % pi == "3.141592653589793"  -- GHC
+  , "%.8x" % pi == "3.243f6a89"
+  , "%.12b" % pi == "11.001001000100"
+
+
+-- TODO: in the future, also handle Ratios nicely:
+-- NOTE: use 360 or 5040 as a default divisor (max precision)
+-- '/' can be just an alias for '.'
+--, "%q" % (2/3 :: Rational) == "2/3"
+--, "%q" % (2/3 :: Rational) == "2/3"
+--, "%Q" % (2/3 :: Rational) == "2 % 3"
+--, "%q" % 0.333 == "1/3"
+--, "%q" % 0 == "0/1"
+--, "%q" % 1 == "1/1"
+--, "%q" % 2 == "2/1"
+--, "%/12q" % 0.333 == "1/3"
+--, "%/3q" % (3/12) == "1/3"
+--, "%4/3q" % (3/12) == " 1/3"
+-- why not use /N for exact precision?
+-- one can just format manually using %i/%i
+-- or even use %/*q
+
+  -- error handling
+  , "%i"   % "a" == "!"
+  , "%d"   % "a" == "!"
+  , "%x"   % "a" == "!"
+  , "%o"   % "a" == "!"
+  , "%b"   % "a" == "!"
+  , "%f"   % "a" == "!"
+  , "%1i"  % "b" == "!"
+  , "%2d"  % "c" == "!!"
+  , "%3x"  % " " == "!!!"
+  , "%4o"  % "!" == "!!!!"
+  , "%5b"  % "." == "!!!!!"
+  , "%6f"  % "-" == "!!!!!!"
+  , "%01f" % "A" == "!"
+  , "%02b" % "B" == "!!"
+  , "%03o" % "C" == "!!!"
+  , "%04x" % "%" == "!!!!"
+  , "%05d" % "/" == "!!!!!"
+  , "%06i" % "^" == "!!!!!!"
+  ]
diff --git a/tests/test-prop.hs b/tests/test-prop.hs
new file mode 100644
--- /dev/null
+++ b/tests/test-prop.hs
@@ -0,0 +1,26 @@
+-- Copyright (c) 2018 Rudy Matela.
+-- Distributed under the 3-Clause BSD licence (see the file LICENSE).
+import Test
+import Text.PercentFormat
+import Test.LeanCheck
+
+main :: IO ()
+main = reportTests (tests 10000)
+
+tests :: Int -> [Bool]
+tests n =
+  [ True
+  , holds n $ \s -> s == "%s" -% s
+  , holds n $ \s t -> s ++ t == "%s%s" % s -% t
+  , holds n $ \s t -> s ++ "%" ++ t == "%s%%%s" % s -% t
+  , holds n $ \s -> s ++ s == "%s%s" % s -% s
+  , holds n $ \s1 s2 s3 -> s1 ++ s2 ++ s3 == (s1 ++ "%s" ++ s3) -% s2
+  , holds n $ \s -> show s == "%r" -% (s::String)
+  , holds n $ \x -> show x == "%r" -% (x::Int)
+  , holds n $ \c -> show c == "%r" -% (c::Char)
+  , holds n $ \f -> show f == "%r" -% (f::Float)
+  , holds n $ \i -> show i == "%i" -% (i::Int)
+  , holds n $ \f -> 'e' `notElem` show f -- TODO: == %f or == %e
+                ==> show f == "%f" -% (f::Float)
+  , holds n $ \c -> c:""   == "%c" -% (c::Char)
+  ]
diff --git a/tests/test-quotient.hs b/tests/test-quotient.hs
new file mode 100644
--- /dev/null
+++ b/tests/test-quotient.hs
@@ -0,0 +1,56 @@
+-- Copyright (c) 2018 Rudy Matela.
+-- Distributed under the 3-Clause BSD licence (see the file LICENSE).
+import Test
+
+import Test.LeanCheck
+import Test.LeanCheck.Utils
+
+import Text.PercentFormat.Quotient as Q
+import Text.PercentFormat.Utils
+
+quotient :: Quotient
+quotient  =  error "dummy quotient value"
+
+instance Listable Quotient where
+  tiers  =  mapT (uncurry (Q.%)) . reset
+         $  tiers `suchThat` canonical
+    where
+    --canonical (0,0)  =  True
+    --canonical (1,0)  =  True
+    canonical (n,d)  =  d > 0 && n `gcd` d == 1
+
+main :: IO ()
+main = reportTests (tests 360)
+
+tests :: Int -> [Bool]
+tests n =
+  [ True
+
+  , holds n $ commutative ((+) -:> quotient)
+  , holds n $ associative ((+) -:> quotient)
+  , holds n $ commutative ((*) -:> quotient)
+  , holds n $ associative ((*) -:> quotient)
+  , holds n $ distributive (*) ((+) -:> quotient)
+  , holds n $ idempotent (+0) -:> quotient
+  , holds n $ idempotent (*1) -:> quotient
+  , holds n $ idempotent abs -:> quotient
+  , holds n $ idempotent signum -:> quotient
+  , holds n $ \x -> negate (negate x) == (x -: quotient)
+  , holds n $ \x -> abs x * signum x == (x -: quotient)
+
+  , holds n $ \q r s -> none Q.isNaN [q,r,s] ==> okEqOrd q r s
+--, holds n $ okNum -:> quotient  -- TODO: when new LeanCheck is released
+
+  , readQ "Infinity" == infinity
+  , readQ "-Infinity" == -infinity
+  , let nan' = readQ "NaN" in nan' /= nan'
+  , maybeReadQ "blah" == Nothing
+  , readQ "10" == 10
+  , readQ "-300" == (-300)
+  , readQ "10.10" == 101 % 10
+  , readQ "10.1" == 101 % 10
+  , readQ "33.3e2" == 3330
+  , readQ "3.33e2" == 333
+  , round (1/2 :: Quotient) == 0
+  , round (3/2 :: Quotient) == 2
+  ]
diff --git a/tests/test-scientific.hs b/tests/test-scientific.hs
new file mode 100644
--- /dev/null
+++ b/tests/test-scientific.hs
@@ -0,0 +1,56 @@
+-- Copyright (c) 2018 Rudy Matela.
+-- Distributed under the 3-Clause BSD licence (see the file LICENSE).
+import Test
+
+import Text.PercentFormat
+import Data.List (isInfixOf)
+
+import qualified Data.Ratio as R
+
+main :: IO ()
+main = reportTests tests
+
+tests :: [Bool]
+tests =
+  [ True
+
+{-
+  -- TODO: make the following pass:
+  , "%e" -% 0 == "0.0e0"
+  , "%e" -% 1 == "1.0e0"
+  , "%e" -% 2 == "2.0e0"
+  , "%e" -% 0.1   == "1.0e-1"
+  , "%e" -% 0.12  == "1.2e-1"
+  , "%e" -% 0.123 == "1.23e-1"
+  , "%e" -% 1.0   == "1.0e0"
+  , "%e" -% 1.2   == "1.2e0"
+  , "%e" -% 1.23  == "1.23e0"
+  , "%e" -% 10.0  == "1.0e1"
+  , "%e" -% 12.0  == "1.2e1"
+  , "%e" -% 12.3  == "1.23e1"
+  , "%.1e" -% 0     == "0.0e0"
+  , "%.1e" -% 1     == "1.0e0"
+  , "%.1e" -% 2     == "2.0e0"
+  , "%.1e" -% 0.1   == "1.0e-1"
+  , "%.1e" -% 0.12  == "1.2e-1"
+  , "%.1e" -% 0.123 == "1.2e-1"
+  , "%.1e" -% 1.0   == "1.0e0"
+  , "%.1e" -% 1.2   == "1.2e0"
+  , "%.1e" -% 1.23  == "1.2e0"
+  , "%.1e" -% 10.0  == "1.0e1"
+  , "%.1e" -% 12.0  == "1.2e1"
+  , "%.1e" -% 12.3  == "1.2e1"
+  , "%.2e" -% 0     == "0.00e0"
+  , "%.2e" -% 1     == "1.00e0"
+  , "%.2e" -% 2     == "2.00e0"
+  , "%.2e" -% 0.1   == "1.00e-1"
+  , "%.2e" -% 0.12  == "1.20e-1"
+  , "%.2e" -% 0.123 == "1.23e-1"
+  , "%.2e" -% 1.0   == "1.00e0"
+  , "%.2e" -% 1.2   == "1.20e0"
+  , "%.2e" -% 1.23  == "1.23e0"
+  , "%.2e" -% 10.0  == "1.00e1"
+  , "%.2e" -% 12.0  == "1.20e1"
+  , "%.2e" -% 12.3  == "1.23e1"
+-}
+  ]
diff --git a/tests/test-sdist b/tests/test-sdist
new file mode 100644
--- /dev/null
+++ b/tests/test-sdist
@@ -0,0 +1,31 @@
+#!/bin/bash
+#
+# test-sdist: tests the package generated by "cabal sdist".
+#
+# Copyright (c) 2015-2018 Rudy Matela.
+# Distributed under the 3-Clause BSD licence.
+#
+# TODO: also run "make test"
+# TODO: also run "stack test"
+pkgver=` cat *.cabal | grep "^version:" | sed -e "s/version: *//"`
+pkgname=`cat *.cabal | grep "^name:"    | sed -e "s/name: *//"`
+pkg=$pkgname-$pkgver
+set -x
+cabal sdist &&
+cd dist &&
+rm -rf $pkg/ &&
+tar -xzf $pkg.tar.gz &&
+cd $pkg/ &&
+cabal configure --enable-tests --enable-benchmarks &&
+cabal build &&
+cabal test &&
+cd .. &&
+if [ -d ../.git ]
+then
+	git -C .. ls-files                                      | sort > ls-git   &&
+	tar -tf $pkg.tar.gz | grep -v "/$" | sed -e "s,$pkg/,," | sort > ls-cabal &&
+	diff -rud ls-git ls-cabal &&
+	rm -f ls-git ls-cabal
+else
+	true # skip test
+fi
diff --git a/tests/test-spec.hs b/tests/test-spec.hs
new file mode 100644
--- /dev/null
+++ b/tests/test-spec.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2018 Rudy Matela.
+-- Distributed under the 3-Clause BSD licence (see the file LICENSE).
+import Test
+
+import Text.PercentFormat.Spec
+
+main :: IO ()
+main = reportTests tests
+
+tests :: [Bool]
+tests =
+  [ True
+  , parseSpec "%"    ==- spec {ty = Percent}
+  , parseSpec "r"    ==- spec {ty = ReprSpec}
+  , parseSpec "s"    ==- spec {ty = StringSpec}
+  , parseSpec "10s"  ==- spec {ty = StringSpec,  width = 10}
+  , parseSpec "21s"  ==- spec {ty = StringSpec,  width = 21}
+  , parseSpec "d"    ==- spec {ty = NumberSpec}
+  , parseSpec "4d"   ==- spec {ty = NumberSpec, width =  4,  base = 10}
+  , parseSpec "6d"   ==- spec {ty = NumberSpec, width =  6,  base = 10}
+  , parseSpec "07d"  ==- spec {ty = NumberSpec, width =  7,  padWith = '0'}
+  , parseSpec "073d" ==- spec {ty = NumberSpec, width =  73, padWith = '0'}
+  , parseSpec "6i"   ==- spec {ty = NumberSpec, width =  6,  base = 10, precision = Just 0}
+  , parseSpec "07i"  ==- spec {ty = NumberSpec, width =  7,  padWith = '0', precision = Just 0}
+  , parseSpec "07.*i" ==- spec {ty = NumberSpec, width =  7,  padWith = '0'}
+  , parseSpec "073i" ==- spec {ty = NumberSpec, width =  73, padWith = '0', precision = Just 0}
+  , parseSpec "x"    ==- spec {ty = NumberSpec, base = 16}
+  , parseSpec "X"    ==- spec {ty = NumberSpec, base = 16, capitalizeDigits = True}
+  , parseSpec "o"    ==- spec {ty = NumberSpec, base =  8}
+  , parseSpec "b"    ==- spec {ty = NumberSpec, base =  2}
+  , parseSpec "d"    ==- spec {ty = NumberSpec, positivePrefix=""}
+  , parseSpec "+d"   ==- spec {ty = NumberSpec, positivePrefix="+"}
+  , parseSpec " d"   ==- spec {ty = NumberSpec, positivePrefix=" "}
+  , parseSpec "i"    ==- spec {ty = NumberSpec, positivePrefix="", precision = Just 0}
+  , parseSpec "+i"   ==- spec {ty = NumberSpec, positivePrefix="+", precision = Just 0}
+  , parseSpec " i"   ==- spec {ty = NumberSpec, positivePrefix=" ", precision = Just 0}
+  , parseSpec ".*i"  ==- spec {ty = NumberSpec, positivePrefix=""}
+  , parseSpec "f"    ==- spec {ty = NumberSpec, minPrecision = 1}
+  , parseSpec "3f"   ==- spec {ty = NumberSpec, minPrecision = 1, width = 3}
+  , parseSpec "4.1f" ==- spec {ty = NumberSpec, minPrecision = 1, width = 4, precision = Just 1}
+  , parseSpec "4.0f" ==- spec {ty = NumberSpec, minPrecision = 1, width = 4, precision = Just 0}
+  , parseSpec "4.f"  ==- spec {ty = NumberSpec, minPrecision = 1, width = 4, precision = Just 0}
+  , parseSpec "f"    ==- spec {ty = NumberSpec, minPrecision = 1, positivePrefix=""}
+  , parseSpec "+f"   ==- spec {ty = NumberSpec, minPrecision = 1, positivePrefix="+"}
+  , parseSpec " f"   ==- spec {ty = NumberSpec, minPrecision = 1, positivePrefix=" "}
+    ]
+  where
+  x ==- y = x == (y,"")
diff --git a/tests/test-unit.hs b/tests/test-unit.hs
new file mode 100644
--- /dev/null
+++ b/tests/test-unit.hs
@@ -0,0 +1,49 @@
+-- Copyright (c) 2018 Rudy Matela.
+-- Distributed under the 3-Clause BSD licence (see the file LICENSE).
+import Test
+
+import Text.PercentFormat
+
+main :: IO ()
+main = reportTests tests
+
+tests :: [Bool]
+tests =
+  [ True
+
+  , "asdf qwer"      % "asdf" ==  "asdf qwer"
+  , "zxcv qwer"      % "asdf" ==  "zxcv qwer"
+  , "asdf %s qwer"   % "asdf" ==  "asdf asdf qwer"
+  , "asdf %5s qwer"  % "asdf" ==  "asdf  asdf qwer"
+  , "asdf %5-s qwer" % "asdf" ==  "asdf asdf  qwer"
+  , "%s" % "1%" == "1%%"
+  , "%s" -% "1%" == "1%"
+
+  , "asdf %d qwer"   % 10     ==  "asdf 10 qwer"
+  , "asdf %2d qwer"  %  9     ==  "asdf  9 qwer"
+  , "asdf %02d qwer" %  9     ==  "asdf 09 qwer"
+  , "asdf %012d qwer" % 123   ==  "asdf 000000000123 qwer"
+
+  , "asdf %x qwer" % 10       ==  "asdf a qwer"
+  , "asdf %04x qwer" % 10     ==  "asdf 000a qwer"
+  , "asdf %o qwer"   % 10     ==  "asdf 12 qwer"
+  , "asdf %03o qwer" % 10     ==  "asdf 012 qwer"
+  , "%b" % 10 == "1010"
+  , "%08b" % 10 == "00001010"
+  , "asdf %X qwer" % 10       ==  "asdf A qwer"
+
+  , "%c" % 'c' == "c"
+  , "%r" % 'c' == "'c'"
+
+  , "%s" % 10 == "10"
+
+  , "%c"   % "a" == "!"
+  , "%c"   % 3   == "!"
+  , "%c"   % 3.1 == "!"
+
+  -- not really error:
+  , "%s"   % 0    == "0"
+  , "%s"   % 3.14 == "3.14"
+  , "%2s"  % 0    == " 0"
+  , "%2s"  % 3.14 == "3.14"
+  ]
