diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2013-2014, Spiros Eliopoulos
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the author nor the names of his contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE 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 AUTHORS 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,108 @@
+htoml
+=====
+
+A [TOML](https://github.com/mojombo/toml) parser library in
+[Haskell](http://haskell-lang.org).
+
+TOML is the obvious, minimal configuration language by
+[Tom Preston-Werner](https://github.com/mojombo).
+It is an alternative to the [XML](http://www.w3.org/TR/REC-xml/),
+[YAML](http://www.yaml.org/spec/1.2/spec.html) and
+[INI](http://en.wikipedia.org/wiki/INI_file) formats for the purpose of
+configuration files, as the first two are too heavy for that prupose,
+and the latter is underspecified.
+Toml is to configuration files, like what Markdown is for rich-text.
+
+This library aims to be compatible with the latest version of the
+[TOML spec](https://github.com/mojombo/toml), currently that is
+[v0.3.1](https://github.com/toml-lang/toml/releases/tag/v0.3.1).
+
+The documentation for this package may (or may not) be found on
+[Hackage](https://hackage.haskell.org/package/htoml).
+
+
+### Quick start
+
+To quickly show some features of `htoml` we use `GHCi`, from the
+root of the repository run:
+
+    cabal repl
+
+It picks up configuration from the `.ghci` file, so we can immediately
+start exploring:
+
+    > txt <- readFile "benchmarks/example.toml"
+    > let r = parseTomlDoc "" txt
+    > r
+    Right (fromList [("database",NTable (fromList [("enabled",NTValue (VBoolean True) [...]
+
+    > let Right toml = r
+    > toJSON toml
+    Object (fromList [("database",Object (fromList [("enabled",Bool True) [...]
+
+    > let Left error = parseTomlDoc "" "== invalid toml =="
+    > error
+    (line 1, column 1):
+    unexpected '='
+    expecting "#", "[" or end of input
+
+First notice that some outputs are truncated.
+
+
+### Tests and benchmarks
+
+The test suite is build by default, `cabal configure --disable-tests` disables them.
+The benchmark suite is not run by default, `cabal configure --enable-benchmarks` enables them.
+
+With `cabal build` both of these suites are build as executables and
+put somewhere in `dist/`. Passing `--help` to them will reveal their
+options.
+
+[BurntSushi's language agnostic test suite](https://github.com/BurntSushi/toml-test)
+is embedded in the test suite executable.  Using a shell script (that
+lives in `test/BurntSushi`) the latest tests can be fetched from
+BurntSushi's repository.
+
+
+### Contributions
+
+Most welcome! Please raise issues, start discussions, give comments or
+submit pull-requests.
+This is one of the first Haskell libraries I wrote, any feedback is
+much appreciated.
+
+
+### Features
+
+* Follows the latest version of the TOML spec, proven by an extensive test suite
+* Incorporates [BurntSushi's language agnostic test suite](https://github.com/BurntSushi/toml-test)
+* Has an internal representation that easily maps to JSON
+* Provides a JSON interface (suggested by Greg Weber)
+* Useful error messages (thanks to using Parsec over Attoparsec)
+* Understands arrays as described in [this issue](https://github.com/toml-lang/toml/issues/254)
+* Provides a benchmark suite
+* Fails on mix-type arrays (as per spec)
+* Haddock documentation
+
+
+### Todo
+
+* Release a stable 1.0 release and submit it to [Stackage](http://stackage.org)
+* More documentation
+* Moke all tests pass (currently some more obscure corner cases don't pass)
+* Add more tests (maybe find a more mature TOML parser and steal their tests)
+* Add property tests with QuickCheck (the internet says it's possible for parsers)
+* Extensively test error cases
+* Try using Vector instead of List (measure performance increase with the benchmarks)
+* See how lenses may (or may not) fit into this package
+
+
+### Acknoledgements
+
+Originally this project started off by improving the `toml` package by
+Spiros Eliopoulos.
+
+
+### License
+
+BSD3 as found in the `LICENSE` file.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,8 @@
+#!/usr/bin/env runghc
+
+> module Main where
+
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/benchmarks/Benchmarks.hs b/benchmarks/Benchmarks.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/Benchmarks.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import           Prelude        hiding (readFile)
+
+import           Criterion.Main
+import           Data.Text.IO   (readFile)
+
+import           Text.Toml
+
+
+main :: IO ()
+main = do
+    exampleToml  <- readFile "./benchmarks/example.toml"
+    repeatedToml <- readFile "./benchmarks/repeated.toml"
+    defaultMain
+
+      [ bgroup "string"
+        [ bench "assignment"  $ whnf (parseTomlDoc "") "q=42\nqa=[4,2,]\nqb=true\nqf=23.23\n\
+                                                       \qd=1979-05-27T07:32:00Z\nqs='forty-two'"
+        , bench "headers"     $ whnf (parseTomlDoc "") "[Q]\n[A]\n[[QA]]\n[[QA]]\n[[QA]]\n[[QA]]"
+        , bench "mixed"       $ whnf (parseTomlDoc "") "q=42\nqa=[4,2,]\n[Q]qq=42\n[[QA]]\n[[QA]]"
+        ]
+
+      , bgroup "file"
+        [ bench "example"     $ whnf (parseTomlDoc "") exampleToml
+        , bench "repeated-4x" $ whnf (parseTomlDoc "") repeatedToml
+        ]
+
+      ]
diff --git a/benchmarks/example.toml b/benchmarks/example.toml
new file mode 100644
--- /dev/null
+++ b/benchmarks/example.toml
@@ -0,0 +1,48 @@
+# This is a TOML document. Boom.
+
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers1]
+
+  # You can indent as you please. Tabs or spaces. TOML don't care.
+  [servers.alpha]
+  ip = "10.0.0.1"
+  dc = "eqdc10"
+
+  [servers.beta]
+  ip = "10.0.0.2"
+  dc = "eqdc10"
+  country = "中国" # This should be parsed as UTF-8
+
+[clients]
+data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
+
+
+# Line breaks are OK when inside arrays
+hosts = [
+  "alpha",
+  "omega"
+]
+
+# Products
+
+  [[products]]
+  name = "Hammer"
+  sku = 738594937
+
+  [[products]]
+  name = "Nail"
+  sku = 284758393
+  color = "gray"
diff --git a/benchmarks/repeated.toml b/benchmarks/repeated.toml
new file mode 100644
--- /dev/null
+++ b/benchmarks/repeated.toml
@@ -0,0 +1,194 @@
+# This is a TOML document. Boom.
+
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers1]
+
+  # You can indent as you please. Tabs or spaces. TOML don't care.
+  [servers.alpha]
+  ip = "10.0.0.1"
+  dc = "eqdc10"
+
+  [servers.beta]
+  ip = "10.0.0.2"
+  dc = "eqdc10"
+  country = "中国" # This should be parsed as UTF-8
+
+[clients]
+data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
+
+
+# Line breaks are OK when inside arrays
+hosts = [
+  "alpha",
+  "omega"
+]
+
+# Products
+
+  [[products]]
+  name = "Hammer"
+  sku = 738594937
+
+  [[products]]
+  name = "Nail"
+  sku = 284758393
+  color = "gray"
+
+
+# This is a TOML document. Boom.
+
+[owner2]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database2]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers2]
+
+  # You can indent as you please. Tabs or spaces. TOML don't care.
+  [servers.alpha2]
+  ip = "10.0.0.1"
+  dc = "eqdc10"
+
+  [servers.beta2]
+  ip = "10.0.0.2"
+  dc = "eqdc10"
+  country = "中国" # This should be parsed as UTF-8
+
+[clients2]
+data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
+
+
+# Line breaks are OK when inside arrays
+hosts = [
+  "alpha",
+  "omega"
+]
+
+# Products
+
+  [[products2]]
+  name = "Hammer"
+  sku = 738594937
+
+  [[products2]]
+  name = "Nail"
+  sku = 284758393
+  color = "gray"
+
+
+
+# This is a TOML document. Boom.
+
+[owner3]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database3]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers3]
+
+  # You can indent as you please. Tabs or spaces. TOML don't care.
+  [servers.alpha3]
+  ip = "10.0.0.1"
+  dc = "eqdc10"
+
+  [servers.beta3]
+  ip = "10.0.0.2"
+  dc = "eqdc10"
+  country = "中国" # This should be parsed as UTF-8
+
+[clients3]
+data = [ ["gamma", "delta"], [1, 3] ] # just an update to make sure parsers support it
+
+
+# Line breaks are OK when inside arrays
+hosts = [
+  "alpha",
+  "omega"
+]
+
+# Products
+
+  [[products3]]
+  name = "Hammer"
+  sku = 738594937
+
+  [[products3]]
+  name = "Nail"
+  sku = 284758393
+  color = "gray"
+
+
+
+# This is a TOML document. Boom.
+
+[owner4]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database4]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers4]
+
+  # You can indent as you please. Tabs or spaces. TOML don't care.
+  [servers.alpha4]
+  ip = "10.0.0.1"
+  dc = "eqdc10"
+
+  [servers.beta4]
+  ip = "10.0.0.2"
+  dc = "eqdc10"
+  country = "中国" # This should be parsed as UTF-8
+
+[clients4]
+data = [ ["gamma", "delta"], [1, 4] ] # just an update to make sure parsers support it
+
+
+# Line breaks are OK when inside arrays
+hosts = [
+  "alpha",
+  "omega"
+]
+
+# Products
+
+  [[products4]]
+  name = "Hammer"
+  sku = 738594937
+
+  [[products4]]
+  name = "Nail"
+  sku = 284758393
+  color = "gray"
diff --git a/htoml.cabal b/htoml.cabal
new file mode 100644
--- /dev/null
+++ b/htoml.cabal
@@ -0,0 +1,113 @@
+name:                   htoml
+version:                0.1.0.0
+synopsis:               A parser for TOML files.
+description:            TOML is a obvious and minimal format for config files.
+                        .
+                        This package provides a TOML parser,
+                        build with the Parsec library of parser combinators.
+                        .
+                        It provides a JSON interface using the Aeson library.
+homepage:               https://github.com/cies/htoml
+bug-reports:            https://github.com/cies/htoml/issues
+license:                BSD3
+license-file:           LICENSE
+author:                 Cies Breijs
+maintainer:             Cies Breijs
+category:               Text
+build-type:             Simple
+cabal-version:          >= 1.10
+extra-source-files:     README.md
+                        , test/BurntSushi/fetch-toml-tests.sh
+                        , test/BurntSushi/valid/*.toml
+                        , test/BurntSushi/valid/*.json
+                        , test/BurntSushi/invalid/*.toml
+                        , benchmarks/example.toml
+                        , benchmarks/repeated.toml
+
+
+source-repository       head
+  type:                 git
+  location:             https://github.com/cies/htoml.git
+
+library
+  exposed-modules:      Text.Toml
+                        , Text.Toml.Parser
+                        , Text.Toml.Types
+  ghc-options:          -Wall
+  hs-source-dirs:       src
+  default-language:     Haskell2010
+  build-depends:        base             >= 4.6  && < 5
+                        , parsec         >= 3.1.5
+                        , containers     >= 0.5  && < 0.6
+                        , unordered-containers
+                        , vector
+                        , aeson
+                        , text
+                        , time           -any
+                        , old-locale     -any
+
+executable tests
+  hs-source-dirs:       test
+  ghc-options:          -Wall
+  main-is:              Test.hs
+  other-modules:        BurntSushi
+                        , BurntSushi
+                        , Text.Toml.Parser.Spec
+  default-language:     Haskell2010
+  build-depends:        base             >= 4.6  && < 5
+                        , parsec         >= 3.1.5
+                        , containers     >= 0.5  && < 0.6
+                        , bytestring
+                        , file-embed
+                        , unordered-containers
+                        , vector
+                        , aeson
+                        , text
+                        , time           -any
+                        , Cabal          >= 1.16.0
+                        , htoml
+                        , tasty
+                        , tasty-hspec
+                        , tasty-hunit
+
+test-suite Tests
+  hs-source-dirs:       test
+  ghc-options:          -Wall
+  main-is:              Test.hs
+  other-modules:        BurntSushi
+                        , BurntSushi
+                        , Text.Toml.Parser.Spec
+  type:                 exitcode-stdio-1.0
+  default-language:     Haskell2010
+  build-depends:        base             >= 4.6  && < 5
+                        , parsec         >= 3.1.5
+                        , containers     >= 0.5  && < 0.6
+                        , bytestring
+                        , file-embed
+                        , unordered-containers
+                        , vector
+                        , text
+                        , time           -any
+                        , Cabal          >= 1.16.0
+                        , htoml
+                        , tasty
+                        , tasty-hspec
+                        , tasty-hunit
+
+benchmark benchmarks
+  hs-source-dirs:       benchmarks .
+  ghc-options:          -O2 -Wall -rtsopts
+  main-is:              Benchmarks.hs
+  type:                 exitcode-stdio-1.0
+  default-language:     Haskell2010
+  build-depends:        base             >= 4.6  && < 5
+                        , parsec         >= 3.1.5
+                        , containers     >= 0.5  && < 0.6
+                        , unordered-containers
+                        , vector
+                        , aeson
+                        , text
+                        , time           -any
+                        , Cabal          >= 1.16.0
+                        , htoml
+                        , criterion
diff --git a/src/Text/Toml.hs b/src/Text/Toml.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Toml.hs
@@ -0,0 +1,15 @@
+module Text.Toml where
+
+import           Prelude          hiding (readFile)
+
+import           Data.Text        (Text)
+import           Text.Parsec
+
+import           Text.Toml.Parser
+
+
+-- | Parse a 'Text' that results in 'Either' a 'String'
+-- containing the error message, or an internal representation
+-- of the document in the 'Toml' data type.
+parseTomlDoc :: String -> Text -> Either ParseError Table
+parseTomlDoc inputName input = parse tomlDoc inputName input
diff --git a/src/Text/Toml/Parser.hs b/src/Text/Toml/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Toml/Parser.hs
@@ -0,0 +1,265 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Text.Toml.Parser
+  ( module Text.Toml.Parser
+  , module Text.Toml.Types
+  ) where
+
+
+import           Prelude             hiding (concat, takeWhile)
+
+import           Control.Applicative hiding (many, optional, (<|>))
+import qualified Data.HashMap.Strict as M
+import qualified Data.List           as L
+import qualified Data.Set            as S
+import           Data.Text           (Text, concat, pack, unpack)
+import           Data.Time.Format    (parseTime)
+import           Numeric             (readHex)
+import           System.Locale       (defaultTimeLocale, iso8601DateFormat)
+import           Text.Parsec
+import           Text.Parsec.Text
+
+import           Text.Toml.Types
+
+
+
+-- | Convenience function for the test suite and GHCI.
+parseOnly :: Parser a -> Text -> Either ParseError a
+parseOnly p str = parse (p <* eof) "test" str
+
+
+-- | Parses a complete document formatted according to the TOML spec.
+tomlDoc :: Parser Table
+tomlDoc = do
+    skipBlanks
+    topTable <- table
+    namedSections <- many namedSection
+    eof  -- ensures input is completely consumed
+    case join topTable (reverse namedSections) of
+      Left msg -> fail (unpack msg)  -- TODO: allow Text in Parse Errors
+      Right r  -> return $ r
+  where
+    join tbl []     = Right tbl
+    join tbl (x:xs) = case join tbl xs of Left msg -> Left msg
+                                          Right r  -> insert x r
+
+
+-- | Parses a table of key-value pairs.
+table :: Parser Table
+table = do
+    pairs <- try (many (assignment <* skipBlanks)) <|> (try skipBlanks >> return [])
+    case hasDup (map fst pairs) of
+      Just k  -> fail $ "Cannot redefine key " ++ (unpack k)
+      Nothing -> return $ M.fromList (map (\(k, v) -> (k, NTValue v)) pairs)
+  where
+    hasDup        :: Ord a => [a] -> Maybe a
+    hasDup xs     = dup' xs S.empty
+    dup' []     _ = Nothing
+    dup' (x:xs) s = if S.member x s then Just x else dup' xs (S.insert x s)
+
+
+-- | Parses a 'Table' or 'TableArray' with its header.
+-- The resulting tuple has the header's value in the first position, and the
+-- 'NTable' or 'NTArray' in the second.
+namedSection :: Parser ([Text], Node)
+namedSection = do
+    eitherHdr <- try (Left <$> tableHeader) <|> try (Right <$> tableArrayHeader)
+    skipBlanks
+    tbl <- table
+    skipBlanks
+    return $ case eitherHdr of Left  ns -> (ns, NTable   tbl )
+                               Right ns -> (ns, NTArray [tbl])
+
+
+-- | Parses a table header.
+tableHeader :: Parser [Text]
+tableHeader = between (char '[') (char ']') headerValue
+
+
+-- | Parses a table array header.
+tableArrayHeader :: Parser [Text]
+tableArrayHeader = between (twoChar  '[') (twoChar ']') headerValue
+  where
+    twoChar c = count 2 (char c)
+
+
+-- | Parses the value of any header (names separated by dots), into a list of 'Text'.
+headerValue :: Parser [Text]
+headerValue = (pack <$> many1 headerNameChar) `sepBy1` (char '.')
+  where
+    headerNameChar = satisfy (\c -> c /= ' ' && c /= '\t' && c /= '\n' &&
+                                    c /= '[' && c /= ']'  && c /= '.'  && c /= '#')
+
+
+-- | Parses a key-value assignment.
+assignment :: Parser (Text, TValue)
+assignment = do
+    k <- pack <$> many1 keyChar
+    skipBlanks >> char '=' >> skipBlanks
+    v <- value
+    return (k, v)
+  where
+    -- TODO: Follow the spec, e.g.: only first char cannot be '['.
+    keyChar = satisfy (\c -> c /= ' ' && c /= '\t' && c /= '\n' &&
+                             c /= '=' && c /= '#'  && c /= '[')
+
+
+-- | Parses a value.
+value :: Parser TValue
+value = (try array    <?> "array")
+    <|> (try boolean  <?> "boolean")
+    <|> (try anyStr   <?> "string")
+    <|> (try datetime <?> "datetime")
+    <|> (try float    <?> "float")
+    <|> (try integer  <?> "integer")
+
+
+--
+-- | * Toml value parsers
+--
+
+array :: Parser TValue
+array = (try (arrayOf array)    <?> "array of arrays")
+    <|> (try (arrayOf boolean)  <?> "array of booleans")
+    <|> (try (arrayOf anyStr)   <?> "array of strings")
+    <|> (try (arrayOf datetime) <?> "array of datetimes")
+    <|> (try (arrayOf float)    <?> "array of floats")
+    <|> (try (arrayOf integer)  <?> "array of integers")
+
+
+boolean :: Parser TValue
+boolean = VBoolean <$> ( (try . string $ "true")  *> return True  <|>
+                         (try . string $ "false") *> return False )
+
+
+anyStr :: Parser TValue
+anyStr = try multiBasicStr <|> try basicStr <|> try multiLiteralStr <|> try literalStr
+
+
+basicStr :: Parser TValue
+basicStr = VString <$> between dQuote dQuote (fmap pack $ many strChar)
+  where
+    strChar = try escSeq <|> try (satisfy (\c -> c /= '"' && c /= '\\'))
+    dQuote  = char '\"'
+
+
+multiBasicStr :: Parser TValue
+multiBasicStr = VString <$> (openDQuote3 *> (fmap pack $ manyTill strChar dQuote3))
+  where
+    -- | Parse the a tripple-double quote, with possibly a newline attached
+    openDQuote3 = try (dQuote3 <* char '\n') <|> try dQuote3
+    -- | Parse tripple-double quotes
+    dQuote3     = count 3 $ char '"'
+    -- | Parse a string char, accepting escaped codes, ignoring escaped white space
+    strChar     = escWhiteSpc *> (escSeq <|> (satisfy (/= '\\'))) <* escWhiteSpc
+    -- | Parse escaped white space, if any
+    escWhiteSpc = many $ char '\\' >> char '\n' >> (many $ satisfy (\c -> isSpc c || c == '\n'))
+
+
+literalStr :: Parser TValue
+literalStr = VString <$> between sQuote sQuote (pack <$> many (satisfy (/= '\'')))
+  where
+    sQuote = char '\''
+
+
+multiLiteralStr :: Parser TValue
+multiLiteralStr = VString <$> (openSQuote3 *> (fmap pack $ manyTill anyChar sQuote3))
+  where
+    -- | Parse the a tripple-single quote, with possibly a newline attached
+    openSQuote3 = try (sQuote3 <* char '\n') <|> try sQuote3
+    -- | Parse tripple-single quotes
+    sQuote3     = try . count 3 . char $ '\''
+
+
+datetime :: Parser TValue
+datetime = do
+    d <- manyTill anyChar (try $ char 'Z')
+    let  mt = parseTime defaultTimeLocale (iso8601DateFormat $ Just "%X") d
+    case mt of Just t  -> return $ VDatetime t
+               Nothing -> fail "parsing datetime failed"
+
+
+-- | Attoparsec 'double' parses scientific "e" notation; reimplement according to Toml spec.
+float :: Parser TValue
+float = VFloat <$> do
+    n <- intStr
+    char '.'
+    d <- uintStr
+    e <- try (satisfy (\c -> c == 'e' || c == 'E') *> intStr) <|> return "0"
+    return . read . L.concat $ [n, ".", d, "e", e]
+  where
+    sign    = try (string "-") <|> (try (char '+') >> return "") <|> return ""
+    uintStr = many1 digit
+    intStr  = do s <- sign
+                 u <- uintStr
+                 return . L.concat $ [s, u]
+
+
+integer :: Parser TValue
+integer = VInteger <$> (signed $ read <$> (many1 digit))
+
+
+
+--
+-- * Utility functions
+--
+
+-- | Parses the elements of an array, while restricting them to a certain type.
+arrayOf :: Parser TValue -> Parser TValue
+arrayOf p = VArray <$> between (char '[') (char ']') (skipBlanks *> separatedValues)
+  where
+    separatedValues = sepEndBy (skipBlanks *> p <* skipBlanks) comma <* skipBlanks
+    comma           = skipBlanks >> char ',' >> skipBlanks
+
+
+-- | Parser for escape sequences.
+escSeq :: Parser Char
+escSeq = char '\\' *> escSeqChar
+  where
+    escSeqChar =  try (char '"')  *> return '"'
+              <|> try (char '\\') *> return '\\'
+              <|> try (char '/')  *> return '/'
+              <|> try (char 'b')  *> return '\b'
+              <|> try (char 't')  *> return '\t'
+              <|> try (char 'n')  *> return '\n'
+              <|> try (char 'f')  *> return '\f'
+              <|> try (char 'r')  *> return '\r'
+              <|> try (char 'u')  *> unicodeHex 4
+              <|> try (char 'U')  *> unicodeHex 8
+              <?> "escape character"
+
+
+-- | Parser for unicode hexadecimal values of representation length 'n'.
+unicodeHex :: Int -> Parser Char
+unicodeHex n = do
+    h <- count n (satisfy isHex)
+    let v = fst . head . readHex $ h
+    return $ if v <= maxChar then toEnum v else '_'
+  where
+    isHex c = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
+    maxChar = fromEnum (maxBound :: Char)
+
+
+-- | Parser for signs (a plus or a minus).
+signed :: Num a => Parser a -> Parser a
+signed p =  try (negate <$> (char '-' *> p))
+        <|> try (char '+' *> p)
+        <|> try p
+
+
+-- | Parses the (rest of the) line including an EOF, whitespace and comments.
+skipBlanks :: Parser ()
+skipBlanks = skipMany blank
+  where
+    blank   = try ((many1 $ satisfy isSpc) >> return ()) <|> try comment <|> try eol
+    comment = char '#' >> (many $ satisfy (/= '\n')) >> return ()
+
+
+-- | Results in 'True' for whitespace chars, tab or space, according to spec.
+isSpc :: Char -> Bool
+isSpc c = c == ' ' || c == '\t'
+
+
+-- | Parse an EOL, as per TOML spec this is 0x0A a.k.a. '\n' or 0x0D a.k.a. '\r'.
+eol :: Parser ()
+eol = satisfy (\c -> c == '\n' || c == '\r') >> return ()
diff --git a/src/Text/Toml/Types.hs b/src/Text/Toml/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Toml/Types.hs
@@ -0,0 +1,178 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Text.Toml.Types where
+
+import           Data.Aeson.Types
+import qualified Data.HashMap.Strict as M
+import           Data.Int            (Int64)
+import           Data.List           (intersect)
+import           Data.Text           (Text)
+import qualified Data.Text           as T
+import           Data.Time.Clock     (UTCTime)
+import           Data.Time.Format    ()
+import qualified Data.Vector         as V
+
+
+-- | The 'Table' is a mapping ('HashMap') of 'Text' keys to 'Node' values.
+type Table = M.HashMap Text Node
+
+
+-- | A 'Node' may contain a 'TValue', a 'Table' or a table array '[Table]'.
+data Node = NTValue TValue
+          | NTable  Table
+          | NTArray [Table]
+  deriving (Eq, Show)
+
+
+-- | A 'TValue' may contain any type of value that can put in a 'VArray'.
+data TValue = VString   Text
+            | VInteger  Int64
+            | VFloat    Double
+            | VBoolean  Bool
+            | VDatetime UTCTime
+            | VArray    [TValue]
+  deriving (Eq, Show)
+
+
+-- | Contruct an empty 'Table'.
+emptyTable :: Table
+emptyTable = M.empty
+
+
+-- | Contruct an empty 'NTable'.
+emptyNTable :: Node
+emptyNTable = NTable M.empty
+
+
+-- | Inserts a table ('Table') with name ('[Text]') which may be part of
+-- a table array (when 'Bool' is 'True') into a 'Table'.
+-- It may result in an error ('Text') on the 'Left' or a modified table
+-- on the 'Right'.
+insert :: ([Text], Node) -> Table -> Either Text Table
+insert ([], _)         _ = error "FATAL: Cannot call 'insert' without a name."
+insert (_ , NTValue _) _ = error "FATAL: Cannot call 'insert' with a TValue."
+insert ([name], node) ttbl =
+    -- In case 'name' is final
+    case M.lookup name ttbl of
+      Nothing           -> Right $ M.insert name node ttbl
+      Just (NTable t)   -> case node of
+        (NTable nt) -> case merge t nt of
+          Left ds -> Left $ T.concat [ "Cannot redefine key(s) (", (T.intercalate ", " ds)
+                                     , "), from table named '", name, "'." ]
+          Right r -> Right $ M.insert name (NTable r) ttbl
+        (NTArray _) -> commonInsertError node [name]
+      Just (NTArray a)  -> case node of
+        (NTable _) -> commonInsertError node [name]
+        (NTArray na) -> Right $ M.insert name (NTArray $ a ++ na) ttbl
+      Just _            -> commonInsertError node [name]
+insert (fullName@(name:ns), node) ttbl =
+    -- In case 'name' is not final, but a sub-name
+    case M.lookup name ttbl of
+      Nothing           -> case insert (ns, node) emptyTable of
+                             Left msg -> Left msg
+                             Right r  -> Right $ M.insert name (NTable r) ttbl
+      Just (NTable t)   -> case insert (ns, node) t of
+                             Left msg -> Left msg
+                             Right tt -> Right $ M.insert name (NTable tt) ttbl
+      Just (NTArray []) -> error "FATAL: Call to 'insert' found impossibly empty NTArray."
+      Just (NTArray a)  -> case insert (ns, node) (last a) of
+                             Left msg -> Left msg
+                             Right t  -> Right $ M.insert name (NTArray $ (init a) ++ [t]) ttbl
+      Just _            -> commonInsertError node fullName
+
+
+-- | Merge two tables, resulting in an error when overlapping keys are
+-- found ('Left' will contian those keys).  When no overlapping keys are
+-- found the result will contain the union of both tables in a 'Right'.
+merge :: Table -> Table -> Either [Text] Table
+merge existing new = case intersect (M.keys existing) (M.keys new) of
+                       [] -> Right $ M.union existing new
+                       ds -> Left  $ ds
+
+
+-- | Convenience function to construct a common error message for the 'insert' function.
+commonInsertError :: Node -> [Text] -> Either Text Table
+commonInsertError what name =
+  let w = case what of (NTable _) -> "tables"
+                       _          -> "array of tables"
+      n = T.intercalate "." name
+  in  Left $ T.concat ["Cannot insert ", w, " '", n, "' as key already exists."]
+
+
+
+-- * Regular ToJSON instances
+
+-- | 'ToJSON' instances for the 'Node' type that produce Aeson (JSON)
+-- in line with the TOML specification.
+instance ToJSON Node where
+  toJSON (NTValue v) = toJSON v
+  toJSON (NTable v)  = toJSON v
+  toJSON (NTArray v) = toJSON v
+
+
+-- | 'ToJSON' instances for the 'TValue' type that produce Aeson (JSON)
+-- in line with the TOML specification.
+instance ToJSON TValue where
+  toJSON (VString v)   = toJSON v
+  toJSON (VInteger v)  = toJSON v
+  toJSON (VFloat v)    = toJSON v
+  toJSON (VBoolean v)  = toJSON v
+  toJSON (VDatetime v) = toJSON v
+  toJSON (VArray v)    = toJSON v
+
+
+
+-- * Special BurntSushi ToJSON type class and instances
+
+-- | Type class for conversion to BurntSushi-style JSON.
+--
+-- BurntSushi has made a language agnostic test suite available that
+-- this library uses. This test suit expects that values are encoded
+-- as JSON objects with a 'type' and a 'value' member.
+class ToBsJSON a where
+  toBsJSON :: a -> Value
+
+
+-- | Provide a 'toBsJSON' instance to the 'NTArray'.
+instance (ToBsJSON a) => ToBsJSON [a] where
+  toBsJSON = Array . V.fromList . map toBsJSON
+  {-# INLINE toBsJSON #-}
+
+
+-- | Provide a 'toBsJSON' instance to the 'NTable'.
+instance (ToBsJSON v) => ToBsJSON (M.HashMap Text v) where
+  toBsJSON = Object . M.map toBsJSON
+  {-# INLINE toBsJSON #-}
+
+
+-- | 'ToBsJSON' instances for the 'Node' type that produce Aeson (JSON)
+-- in line with BurntSushi's language agnostic TOML test suite.
+instance ToBsJSON Node where
+  toBsJSON (NTValue v) = toBsJSON v
+  toBsJSON (NTable v)  = toBsJSON v
+  toBsJSON (NTArray v) = toBsJSON v
+
+
+-- | 'ToBsJSON' instances for the 'TValue' type that produce Aeson (JSON)
+-- in line with BurntSushi's language agnostic TOML test suite.
+--
+-- As seen in this function, BurntSushi's JSON encoding explicitly
+-- specifies the types of the values.
+instance ToBsJSON TValue where
+  toBsJSON (VString v)   = object [ "type"  .= toJSON ("string" :: String)
+                                  , "value" .= toJSON v ]
+  toBsJSON (VInteger v)  = object [ "type"  .= toJSON ("integer" :: String)
+                                  , "value" .= toJSON (show v) ]
+  toBsJSON (VFloat v)    = object [ "type"  .= toJSON ("float" :: String)
+                                  , "value" .= toJSON (show v) ]
+  toBsJSON (VBoolean v)  = object [ "type"  .= toJSON ("bool" :: String)
+                                  , "value" .= toJSON (if v then "true" else "false" :: String) ]
+  toBsJSON (VDatetime v) = object [ "type"  .= toJSON ("datetime" :: String)
+                                  , "value" .= toJSON (let s = show v
+                                                           z = take (length s - 4) s  ++ "Z"
+                                                           d = take (length z - 10) z
+                                                           t = drop (length z - 9) z
+                                                       in  d ++ "T" ++ t) ]
+  toBsJSON (VArray v)    = object [ "type"  .= toJSON ("array" :: String)
+                                  , "value" .= toBsJSON v ]
diff --git a/test/BurntSushi.hs b/test/BurntSushi.hs
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell   #-}
+
+module BurntSushi (tests) where
+
+import           Test.Tasty           (TestTree, testGroup)
+import           Test.Tasty.HUnit
+
+import           Data.Aeson
+import qualified Data.ByteString      as B
+import           Data.ByteString.Lazy (fromStrict)
+import           Data.FileEmbed
+import           Data.List            (isPrefixOf, isSuffixOf)
+import           Data.Text.Encoding   (decodeUtf8)
+
+import           Text.Toml
+import           Text.Toml.Types
+
+
+allFiles :: [(FilePath, B.ByteString)]
+allFiles = $(embedDir "test/BurntSushi")
+
+
+validPairs :: [(String, (B.ByteString, B.ByteString))]
+validPairs =
+    map (\(tFP, tBS) -> (stripExt tFP, (tBS, jsonCounterpart tFP))) tomlFiles
+  where
+    validFiles = filter (\(f, _) -> "valid" `isPrefixOf` f) allFiles
+    filterOnSuffix sfx = filter (\(f, _) -> sfx `isSuffixOf` f)
+    tomlFiles = filterOnSuffix ".toml" validFiles
+    jsonFiles = filterOnSuffix ".json" validFiles
+    stripExt fp = take (length fp - 5) fp
+    jsonCounterpart tFP =
+      case filter (\(f, _) -> f == stripExt tFP ++ ".json") jsonFiles of
+        []       -> error $ "Could not find a JSON counterpart for: " ++ tFP
+        [(_, j)] -> j
+        _        -> error $ "Expected one, but found several \
+                            \JSON counterparts for: " ++ tFP
+
+
+invalidTomlFiles :: [(FilePath, B.ByteString)]
+invalidTomlFiles = filter (\(f, _) -> "invalid" `isPrefixOf` f) allFiles
+
+
+tests :: IO TestTree
+tests = return $ testGroup "BurntSushi's test suite"
+          [ testGroup "test equality of resulting JSON (valid)" $
+              map (\(fp, (tBS, jBS)) -> testCase fp $ assertIsValid fp tBS jBS) validPairs
+          , testGroup "test parse failures of malformed TOML files (invalid)" $
+              map (\(fp, tBS) -> testCase fp $ assertParseFailure fp tBS) invalidTomlFiles
+          ]
+  where
+    assertIsValid f tomlBS jsonBS =
+      case parseTomlDoc "test" (decodeUtf8 tomlBS) of
+        Left e -> assertFailure $ "Could not parse TOML file: " ++ f ++ ".toml\n" ++ (show e)
+        Right tomlTry -> case eitherDecode (fromStrict jsonBS) of
+          Left _ -> assertFailure $ "Could not parse JSON file: " ++ f ++ ".json"
+          Right jsonCorrect -> assertEqual "" jsonCorrect (toBsJSON tomlTry)
+    assertParseFailure f tomlBS =
+      case parseTomlDoc "test" (decodeUtf8 tomlBS) of
+        Left _ -> return ()
+        Right _ -> assertFailure $ "Parser accepted invalid TOML file: " ++ f ++ ".toml"
diff --git a/test/BurntSushi/fetch-toml-tests.sh b/test/BurntSushi/fetch-toml-tests.sh
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/fetch-toml-tests.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+rm valid/*
+rmdir valid
+rm invalid/*
+rmdir invalid
+
+git clone https://github.com/BurntSushi/toml-test
+
+mv toml-test/tests/valid .
+mv toml-test/tests/invalid .
+
+rm -rf toml-test
diff --git a/test/BurntSushi/invalid/array-mixed-types-arrays-and-ints.toml b/test/BurntSushi/invalid/array-mixed-types-arrays-and-ints.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/array-mixed-types-arrays-and-ints.toml
@@ -0,0 +1,1 @@
+arrays-and-ints =  [1, ["Arrays are not integers."]]
diff --git a/test/BurntSushi/invalid/array-mixed-types-ints-and-floats.toml b/test/BurntSushi/invalid/array-mixed-types-ints-and-floats.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/array-mixed-types-ints-and-floats.toml
@@ -0,0 +1,1 @@
+ints-and-floats = [1, 1.1]
diff --git a/test/BurntSushi/invalid/array-mixed-types-strings-and-ints.toml b/test/BurntSushi/invalid/array-mixed-types-strings-and-ints.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/array-mixed-types-strings-and-ints.toml
@@ -0,0 +1,1 @@
+strings-and-ints = ["hi", 42]
diff --git a/test/BurntSushi/invalid/datetime-malformed-no-leads.toml b/test/BurntSushi/invalid/datetime-malformed-no-leads.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/datetime-malformed-no-leads.toml
@@ -0,0 +1,1 @@
+no-leads = 1987-7-05T17:45:00Z
diff --git a/test/BurntSushi/invalid/datetime-malformed-no-secs.toml b/test/BurntSushi/invalid/datetime-malformed-no-secs.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/datetime-malformed-no-secs.toml
@@ -0,0 +1,1 @@
+no-secs = 1987-07-05T17:45Z
diff --git a/test/BurntSushi/invalid/datetime-malformed-no-t.toml b/test/BurntSushi/invalid/datetime-malformed-no-t.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/datetime-malformed-no-t.toml
@@ -0,0 +1,1 @@
+no-t = 1987-07-0517:45:00Z
diff --git a/test/BurntSushi/invalid/datetime-malformed-no-z.toml b/test/BurntSushi/invalid/datetime-malformed-no-z.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/datetime-malformed-no-z.toml
@@ -0,0 +1,1 @@
+no-z = 1987-07-05T17:45:00
diff --git a/test/BurntSushi/invalid/datetime-malformed-with-milli.toml b/test/BurntSushi/invalid/datetime-malformed-with-milli.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/datetime-malformed-with-milli.toml
@@ -0,0 +1,1 @@
+with-milli = 1987-07-5T17:45:00.12Z
diff --git a/test/BurntSushi/invalid/duplicate-key-table.toml b/test/BurntSushi/invalid/duplicate-key-table.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/duplicate-key-table.toml
@@ -0,0 +1,5 @@
+[fruit]
+type = "apple"
+
+[fruit.type]
+apple = "yes"
diff --git a/test/BurntSushi/invalid/duplicate-keys.toml b/test/BurntSushi/invalid/duplicate-keys.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/duplicate-keys.toml
@@ -0,0 +1,2 @@
+dupe = false
+dupe = true
diff --git a/test/BurntSushi/invalid/duplicate-tables.toml b/test/BurntSushi/invalid/duplicate-tables.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/duplicate-tables.toml
@@ -0,0 +1,2 @@
+[a]
+[a]
diff --git a/test/BurntSushi/invalid/empty-implicit-table.toml b/test/BurntSushi/invalid/empty-implicit-table.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/empty-implicit-table.toml
@@ -0,0 +1,1 @@
+[naughty..naughty]
diff --git a/test/BurntSushi/invalid/empty-table.toml b/test/BurntSushi/invalid/empty-table.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/empty-table.toml
@@ -0,0 +1,1 @@
+[]
diff --git a/test/BurntSushi/invalid/float-no-leading-zero.toml b/test/BurntSushi/invalid/float-no-leading-zero.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/float-no-leading-zero.toml
@@ -0,0 +1,2 @@
+answer = .12345
+neganswer = -.12345
diff --git a/test/BurntSushi/invalid/float-no-trailing-digits.toml b/test/BurntSushi/invalid/float-no-trailing-digits.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/float-no-trailing-digits.toml
@@ -0,0 +1,2 @@
+answer = 1.
+neganswer = -1.
diff --git a/test/BurntSushi/invalid/key-empty.toml b/test/BurntSushi/invalid/key-empty.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/key-empty.toml
@@ -0,0 +1,1 @@
+ = 1
diff --git a/test/BurntSushi/invalid/key-hash.toml b/test/BurntSushi/invalid/key-hash.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/key-hash.toml
@@ -0,0 +1,1 @@
+a# = 1
diff --git a/test/BurntSushi/invalid/key-newline.toml b/test/BurntSushi/invalid/key-newline.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/key-newline.toml
@@ -0,0 +1,2 @@
+a
+= 1
diff --git a/test/BurntSushi/invalid/key-open-bracket.toml b/test/BurntSushi/invalid/key-open-bracket.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/key-open-bracket.toml
@@ -0,0 +1,1 @@
+[abc = 1
diff --git a/test/BurntSushi/invalid/key-single-open-bracket.toml b/test/BurntSushi/invalid/key-single-open-bracket.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/key-single-open-bracket.toml
@@ -0,0 +1,1 @@
+[
diff --git a/test/BurntSushi/invalid/key-start-bracket.toml b/test/BurntSushi/invalid/key-start-bracket.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/key-start-bracket.toml
@@ -0,0 +1,3 @@
+[a]
+[xyz = 5
+[b]
diff --git a/test/BurntSushi/invalid/key-two-equals.toml b/test/BurntSushi/invalid/key-two-equals.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/key-two-equals.toml
@@ -0,0 +1,1 @@
+key= = 1
diff --git a/test/BurntSushi/invalid/string-bad-byte-escape.toml b/test/BurntSushi/invalid/string-bad-byte-escape.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/string-bad-byte-escape.toml
@@ -0,0 +1,1 @@
+naughty = "\xAg"
diff --git a/test/BurntSushi/invalid/string-bad-escape.toml b/test/BurntSushi/invalid/string-bad-escape.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/string-bad-escape.toml
@@ -0,0 +1,1 @@
+invalid-escape = "This string has a bad \a escape character."
diff --git a/test/BurntSushi/invalid/string-byte-escapes.toml b/test/BurntSushi/invalid/string-byte-escapes.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/string-byte-escapes.toml
@@ -0,0 +1,1 @@
+answer = "\x33"
diff --git a/test/BurntSushi/invalid/string-no-close.toml b/test/BurntSushi/invalid/string-no-close.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/string-no-close.toml
@@ -0,0 +1,1 @@
+no-ending-quote = "One time, at band camp
diff --git a/test/BurntSushi/invalid/table-array-implicit.toml b/test/BurntSushi/invalid/table-array-implicit.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/table-array-implicit.toml
@@ -0,0 +1,14 @@
+# This test is a bit tricky. It should fail because the first use of
+# `[[albums.songs]]` without first declaring `albums` implies that `albums`
+# must be a table. The alternative would be quite weird. Namely, it wouldn't
+# comply with the TOML spec: "Each double-bracketed sub-table will belong to 
+# the most *recently* defined table element *above* it."
+#
+# This is in contrast to the *valid* test, table-array-implicit where
+# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared
+# later. (Although, `[albums]` could be.)
+[[albums.songs]]
+name = "Glory Days"
+
+[[albums]]
+name = "Born in the USA"
diff --git a/test/BurntSushi/invalid/table-array-malformed-bracket.toml b/test/BurntSushi/invalid/table-array-malformed-bracket.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/table-array-malformed-bracket.toml
@@ -0,0 +1,2 @@
+[[albums]
+name = "Born to Run"
diff --git a/test/BurntSushi/invalid/table-array-malformed-empty.toml b/test/BurntSushi/invalid/table-array-malformed-empty.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/table-array-malformed-empty.toml
@@ -0,0 +1,2 @@
+[[]]
+name = "Born to Run"
diff --git a/test/BurntSushi/invalid/table-empty.toml b/test/BurntSushi/invalid/table-empty.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/table-empty.toml
@@ -0,0 +1,1 @@
+[]
diff --git a/test/BurntSushi/invalid/table-nested-brackets-close.toml b/test/BurntSushi/invalid/table-nested-brackets-close.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/table-nested-brackets-close.toml
@@ -0,0 +1,2 @@
+[a]b]
+zyx = 42
diff --git a/test/BurntSushi/invalid/table-nested-brackets-open.toml b/test/BurntSushi/invalid/table-nested-brackets-open.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/table-nested-brackets-open.toml
@@ -0,0 +1,2 @@
+[a[b]
+zyx = 42
diff --git a/test/BurntSushi/invalid/text-after-array-entries.toml b/test/BurntSushi/invalid/text-after-array-entries.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/text-after-array-entries.toml
@@ -0,0 +1,4 @@
+array = [
+  "Is there life after an array separator?", No
+  "Entry"
+]
diff --git a/test/BurntSushi/invalid/text-after-integer.toml b/test/BurntSushi/invalid/text-after-integer.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/text-after-integer.toml
@@ -0,0 +1,1 @@
+answer = 42 the ultimate answer?
diff --git a/test/BurntSushi/invalid/text-after-string.toml b/test/BurntSushi/invalid/text-after-string.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/text-after-string.toml
@@ -0,0 +1,1 @@
+string = "Is there life after strings?" No.
diff --git a/test/BurntSushi/invalid/text-after-table.toml b/test/BurntSushi/invalid/text-after-table.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/text-after-table.toml
@@ -0,0 +1,1 @@
+[error] this shouldn't be here
diff --git a/test/BurntSushi/invalid/text-before-array-separator.toml b/test/BurntSushi/invalid/text-before-array-separator.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/text-before-array-separator.toml
@@ -0,0 +1,4 @@
+array = [
+  "Is there life before an array separator?" No,
+  "Entry"
+]
diff --git a/test/BurntSushi/invalid/text-in-array.toml b/test/BurntSushi/invalid/text-in-array.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/text-in-array.toml
@@ -0,0 +1,5 @@
+array = [
+  "Entry 1",
+  I don't belong,
+  "Entry 2",
+]
diff --git a/test/BurntSushi/valid/array-empty.json b/test/BurntSushi/valid/array-empty.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/array-empty.json
@@ -0,0 +1,11 @@
+{
+    "thevoid": { "type": "array", "value": [
+        {"type": "array", "value": [
+            {"type": "array", "value": [
+                {"type": "array", "value": [
+                    {"type": "array", "value": []}
+                ]}
+            ]}
+        ]}
+    ]}
+}
diff --git a/test/BurntSushi/valid/array-empty.toml b/test/BurntSushi/valid/array-empty.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/array-empty.toml
@@ -0,0 +1,1 @@
+thevoid = [[[[[]]]]]
diff --git a/test/BurntSushi/valid/array-nospaces.json b/test/BurntSushi/valid/array-nospaces.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/array-nospaces.json
@@ -0,0 +1,10 @@
+{
+    "ints": {
+        "type": "array",
+        "value": [
+            {"type": "integer", "value": "1"},
+            {"type": "integer", "value": "2"},
+            {"type": "integer", "value": "3"}
+        ]
+    }
+}
diff --git a/test/BurntSushi/valid/array-nospaces.toml b/test/BurntSushi/valid/array-nospaces.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/array-nospaces.toml
@@ -0,0 +1,1 @@
+ints = [1,2,3]
diff --git a/test/BurntSushi/valid/arrays-hetergeneous.json b/test/BurntSushi/valid/arrays-hetergeneous.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/arrays-hetergeneous.json
@@ -0,0 +1,19 @@
+{
+    "mixed": {
+        "type": "array",
+        "value": [
+            {"type": "array", "value": [
+                {"type": "integer", "value": "1"},
+                {"type": "integer", "value": "2"}
+            ]},
+            {"type": "array", "value": [
+                {"type": "string", "value": "a"},
+                {"type": "string", "value": "b"}
+            ]},
+            {"type": "array", "value": [
+                {"type": "float", "value": "1.1"},
+                {"type": "float", "value": "2.1"}
+            ]}
+        ]
+    }
+}
diff --git a/test/BurntSushi/valid/arrays-hetergeneous.toml b/test/BurntSushi/valid/arrays-hetergeneous.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/arrays-hetergeneous.toml
@@ -0,0 +1,1 @@
+mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]
diff --git a/test/BurntSushi/valid/arrays-nested.json b/test/BurntSushi/valid/arrays-nested.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/arrays-nested.json
@@ -0,0 +1,13 @@
+{
+    "nest": {
+        "type": "array",
+        "value": [
+            {"type": "array", "value": [
+                {"type": "string", "value": "a"}
+            ]},
+            {"type": "array", "value": [
+                {"type": "string", "value": "b"}
+            ]}
+        ]
+    }
+}
diff --git a/test/BurntSushi/valid/arrays-nested.toml b/test/BurntSushi/valid/arrays-nested.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/arrays-nested.toml
@@ -0,0 +1,1 @@
+nest = [["a"], ["b"]]
diff --git a/test/BurntSushi/valid/arrays.json b/test/BurntSushi/valid/arrays.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/arrays.json
@@ -0,0 +1,34 @@
+{
+    "ints": {
+        "type": "array",
+        "value": [
+            {"type": "integer", "value": "1"},
+            {"type": "integer", "value": "2"},
+            {"type": "integer", "value": "3"}
+        ]
+    },
+    "floats": {
+        "type": "array",
+        "value": [
+            {"type": "float", "value": "1.1"},
+            {"type": "float", "value": "2.1"},
+            {"type": "float", "value": "3.1"}
+        ]
+    },
+    "strings": {
+        "type": "array",
+        "value": [
+            {"type": "string", "value": "a"},
+            {"type": "string", "value": "b"},
+            {"type": "string", "value": "c"}
+        ]
+    },
+    "dates": {
+        "type": "array",
+        "value": [
+            {"type": "datetime", "value": "1987-07-05T17:45:00Z"},
+            {"type": "datetime", "value": "1979-05-27T07:32:00Z"},
+            {"type": "datetime", "value": "2006-06-01T11:00:00Z"}
+        ]
+    }
+}
diff --git a/test/BurntSushi/valid/arrays.toml b/test/BurntSushi/valid/arrays.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/arrays.toml
@@ -0,0 +1,8 @@
+ints = [1, 2, 3]
+floats = [1.1, 2.1, 3.1]
+strings = ["a", "b", "c"]
+dates = [
+  1987-07-05T17:45:00Z,
+  1979-05-27T07:32:00Z,
+  2006-06-01T11:00:00Z,
+]
diff --git a/test/BurntSushi/valid/bool.json b/test/BurntSushi/valid/bool.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/bool.json
@@ -0,0 +1,4 @@
+{
+    "f": {"type": "bool", "value": "false"},
+    "t": {"type": "bool", "value": "true"}
+}
diff --git a/test/BurntSushi/valid/bool.toml b/test/BurntSushi/valid/bool.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/bool.toml
@@ -0,0 +1,2 @@
+t = true
+f = false
diff --git a/test/BurntSushi/valid/comments-everywhere.json b/test/BurntSushi/valid/comments-everywhere.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/comments-everywhere.json
@@ -0,0 +1,12 @@
+{
+    "group": {
+        "answer": {"type": "integer", "value": "42"},
+        "more": {
+            "type": "array",
+            "value": [
+                {"type": "integer", "value": "42"},
+                {"type": "integer", "value": "42"}
+            ]
+        }
+    }
+}
diff --git a/test/BurntSushi/valid/comments-everywhere.toml b/test/BurntSushi/valid/comments-everywhere.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/comments-everywhere.toml
@@ -0,0 +1,24 @@
+# Top comment.
+  # Top comment.
+# Top comment.
+
+# [no-extraneous-groups-please]
+
+[group] # Comment
+answer = 42 # Comment
+# no-extraneous-keys-please = 999
+# Inbetween comment.
+more = [ # Comment
+  # What about multiple # comments?
+  # Can you handle it?
+  #
+          # Evil.
+# Evil.
+  42, 42, # Comments within arrays are fun.
+  # What about multiple # comments?
+  # Can you handle it?
+  #
+          # Evil.
+# Evil.
+# ] Did I fool you?
+] # Hopefully not.
diff --git a/test/BurntSushi/valid/datetime.json b/test/BurntSushi/valid/datetime.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/datetime.json
@@ -0,0 +1,3 @@
+{
+    "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}
+}
diff --git a/test/BurntSushi/valid/datetime.toml b/test/BurntSushi/valid/datetime.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/datetime.toml
@@ -0,0 +1,1 @@
+bestdayever = 1987-07-05T17:45:00Z
diff --git a/test/BurntSushi/valid/empty.json b/test/BurntSushi/valid/empty.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/empty.json
@@ -0,0 +1,1 @@
+{}
diff --git a/test/BurntSushi/valid/empty.toml b/test/BurntSushi/valid/empty.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/empty.toml
diff --git a/test/BurntSushi/valid/example.json b/test/BurntSushi/valid/example.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/example.json
@@ -0,0 +1,14 @@
+{
+  "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"},
+  "numtheory": {
+    "boring": {"type": "bool", "value": "false"},
+    "perfection": {
+      "type": "array",
+      "value": [
+        {"type": "integer", "value": "6"},
+        {"type": "integer", "value": "28"},
+        {"type": "integer", "value": "496"}
+      ]
+    }
+  }
+}
diff --git a/test/BurntSushi/valid/example.toml b/test/BurntSushi/valid/example.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/example.toml
@@ -0,0 +1,5 @@
+best-day-ever = 1987-07-05T17:45:00Z
+
+[numtheory]
+boring = false
+perfection = [6, 28, 496]
diff --git a/test/BurntSushi/valid/float.json b/test/BurntSushi/valid/float.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/float.json
@@ -0,0 +1,4 @@
+{
+    "pi": {"type": "float", "value": "3.14"},
+    "negpi": {"type": "float", "value": "-3.14"}
+}
diff --git a/test/BurntSushi/valid/float.toml b/test/BurntSushi/valid/float.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/float.toml
@@ -0,0 +1,2 @@
+pi = 3.14
+negpi = -3.14
diff --git a/test/BurntSushi/valid/implicit-and-explicit-after.json b/test/BurntSushi/valid/implicit-and-explicit-after.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/implicit-and-explicit-after.json
@@ -0,0 +1,10 @@
+{
+    "a": {
+        "better": {"type": "integer", "value": "43"},
+        "b": {
+            "c": {
+                "answer": {"type": "integer", "value": "42"}
+            }
+        }
+    }
+}
diff --git a/test/BurntSushi/valid/implicit-and-explicit-after.toml b/test/BurntSushi/valid/implicit-and-explicit-after.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/implicit-and-explicit-after.toml
@@ -0,0 +1,5 @@
+[a.b.c]
+answer = 42
+
+[a]
+better = 43
diff --git a/test/BurntSushi/valid/implicit-and-explicit-before.json b/test/BurntSushi/valid/implicit-and-explicit-before.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/implicit-and-explicit-before.json
@@ -0,0 +1,10 @@
+{
+    "a": {
+        "better": {"type": "integer", "value": "43"},
+        "b": {
+            "c": {
+                "answer": {"type": "integer", "value": "42"}
+            }
+        }
+    }
+}
diff --git a/test/BurntSushi/valid/implicit-and-explicit-before.toml b/test/BurntSushi/valid/implicit-and-explicit-before.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/implicit-and-explicit-before.toml
@@ -0,0 +1,5 @@
+[a]
+better = 43
+
+[a.b.c]
+answer = 42
diff --git a/test/BurntSushi/valid/implicit-groups.json b/test/BurntSushi/valid/implicit-groups.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/implicit-groups.json
@@ -0,0 +1,9 @@
+{
+    "a": {
+        "b": {
+            "c": {
+                "answer": {"type": "integer", "value": "42"}
+            }
+        }
+    }
+}
diff --git a/test/BurntSushi/valid/implicit-groups.toml b/test/BurntSushi/valid/implicit-groups.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/implicit-groups.toml
@@ -0,0 +1,2 @@
+[a.b.c]
+answer = 42
diff --git a/test/BurntSushi/valid/integer.json b/test/BurntSushi/valid/integer.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/integer.json
@@ -0,0 +1,4 @@
+{
+    "answer": {"type": "integer", "value": "42"},
+    "neganswer": {"type": "integer", "value": "-42"}
+}
diff --git a/test/BurntSushi/valid/integer.toml b/test/BurntSushi/valid/integer.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/integer.toml
@@ -0,0 +1,2 @@
+answer = 42
+neganswer = -42
diff --git a/test/BurntSushi/valid/key-equals-nospace.json b/test/BurntSushi/valid/key-equals-nospace.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/key-equals-nospace.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "integer", "value": "42"}
+}
diff --git a/test/BurntSushi/valid/key-equals-nospace.toml b/test/BurntSushi/valid/key-equals-nospace.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/key-equals-nospace.toml
@@ -0,0 +1,1 @@
+answer=42
diff --git a/test/BurntSushi/valid/key-space.json b/test/BurntSushi/valid/key-space.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/key-space.json
@@ -0,0 +1,3 @@
+{
+    "a b": {"type": "integer", "value": "1"}
+}
diff --git a/test/BurntSushi/valid/key-space.toml b/test/BurntSushi/valid/key-space.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/key-space.toml
@@ -0,0 +1,1 @@
+   a b    = 1
diff --git a/test/BurntSushi/valid/key-special-chars.json b/test/BurntSushi/valid/key-special-chars.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/key-special-chars.json
@@ -0,0 +1,5 @@
+{
+    "~!@$^&*()_+-`1234567890[]\\|/?><.,;:'": {
+        "type": "integer", "value": "1"
+    }
+}
diff --git a/test/BurntSushi/valid/key-special-chars.toml b/test/BurntSushi/valid/key-special-chars.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/key-special-chars.toml
@@ -0,0 +1,1 @@
+~!@$^&*()_+-`1234567890[]\|/?><.,;:' = 1
diff --git a/test/BurntSushi/valid/long-float.json b/test/BurntSushi/valid/long-float.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/long-float.json
@@ -0,0 +1,4 @@
+{
+    "longpi": {"type": "float", "value": "3.141592653589793"},
+    "neglongpi": {"type": "float", "value": "-3.141592653589793"}
+}
diff --git a/test/BurntSushi/valid/long-float.toml b/test/BurntSushi/valid/long-float.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/long-float.toml
@@ -0,0 +1,2 @@
+longpi = 3.141592653589793
+neglongpi = -3.141592653589793
diff --git a/test/BurntSushi/valid/long-integer.json b/test/BurntSushi/valid/long-integer.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/long-integer.json
@@ -0,0 +1,4 @@
+{
+    "answer": {"type": "integer", "value": "9223372036854776000"},
+    "neganswer": {"type": "integer", "value": "-9223372036854776000"}
+}
diff --git a/test/BurntSushi/valid/long-integer.toml b/test/BurntSushi/valid/long-integer.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/long-integer.toml
@@ -0,0 +1,2 @@
+answer = 9223372036854776000
+neganswer = -9223372036854776000
diff --git a/test/BurntSushi/valid/multiline-string.json b/test/BurntSushi/valid/multiline-string.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/multiline-string.json
@@ -0,0 +1,30 @@
+{
+    "multiline empty one": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline empty two": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline empty three": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline empty four": {
+        "type": "string",
+        "value": ""
+    },
+    "equivalent one": {
+        "type": "string",
+        "value": "The quick brown fox jumps over the lazy dog."
+    },
+    "equivalent two": {
+        "type": "string",
+        "value": "The quick brown fox jumps over the lazy dog."
+    },
+    "equivalent three": {
+        "type": "string",
+        "value": "The quick brown fox jumps over the lazy dog."
+    }
+}
diff --git a/test/BurntSushi/valid/multiline-string.toml b/test/BurntSushi/valid/multiline-string.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/multiline-string.toml
@@ -0,0 +1,23 @@
+multiline empty one = """"""
+multiline empty two = """
+"""
+multiline empty three = """\
+    """
+multiline empty four = """\
+   \
+   \
+   """
+
+equivalent one = "The quick brown fox jumps over the lazy dog."
+equivalent two = """
+The quick brown \
+
+
+  fox jumps over \
+    the lazy dog."""
+
+equivalent three = """\
+       The quick brown \
+       fox jumps over \
+       the lazy dog.\
+       """
diff --git a/test/BurntSushi/valid/raw-multiline-string.json b/test/BurntSushi/valid/raw-multiline-string.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/raw-multiline-string.json
@@ -0,0 +1,14 @@
+{
+    "oneline": {
+        "type": "string",
+        "value": "This string has a ' quote character."
+    },
+    "firstnl": {
+        "type": "string",
+        "value": "This string has a ' quote character."
+    },
+    "multiline": {
+        "type": "string",
+        "value": "This string\nhas ' a quote character\nand more than\none newline\nin it."
+    }
+}
diff --git a/test/BurntSushi/valid/raw-multiline-string.toml b/test/BurntSushi/valid/raw-multiline-string.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/raw-multiline-string.toml
@@ -0,0 +1,9 @@
+oneline = '''This string has a ' quote character.'''
+firstnl = '''
+This string has a ' quote character.'''
+multiline = '''
+This string
+has ' a quote character
+and more than
+one newline
+in it.'''
diff --git a/test/BurntSushi/valid/raw-string.json b/test/BurntSushi/valid/raw-string.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/raw-string.json
@@ -0,0 +1,30 @@
+{
+    "backspace": {
+        "type": "string",
+        "value": "This string has a \\b backspace character."
+    },
+    "tab": {
+        "type": "string",
+        "value": "This string has a \\t tab character."
+    },
+    "newline": {
+        "type": "string",
+        "value": "This string has a \\n new line character."
+    },
+    "formfeed": {
+        "type": "string",
+        "value": "This string has a \\f form feed character."
+    },
+    "carriage": {
+        "type": "string",
+        "value": "This string has a \\r carriage return character."
+    },
+    "slash": {
+        "type": "string",
+        "value": "This string has a \\/ slash character."
+    },
+    "backslash": {
+        "type": "string",
+        "value": "This string has a \\\\ backslash character."
+    }
+}
diff --git a/test/BurntSushi/valid/raw-string.toml b/test/BurntSushi/valid/raw-string.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/raw-string.toml
@@ -0,0 +1,7 @@
+backspace = 'This string has a \b backspace character.'
+tab = 'This string has a \t tab character.'
+newline = 'This string has a \n new line character.'
+formfeed = 'This string has a \f form feed character.'
+carriage = 'This string has a \r carriage return character.'
+slash = 'This string has a \/ slash character.'
+backslash = 'This string has a \\ backslash character.'
diff --git a/test/BurntSushi/valid/string-empty.json b/test/BurntSushi/valid/string-empty.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/string-empty.json
@@ -0,0 +1,6 @@
+{
+    "answer": {
+        "type": "string",
+        "value": ""
+    }
+}
diff --git a/test/BurntSushi/valid/string-empty.toml b/test/BurntSushi/valid/string-empty.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/string-empty.toml
@@ -0,0 +1,1 @@
+answer = ""
diff --git a/test/BurntSushi/valid/string-escapes.json b/test/BurntSushi/valid/string-escapes.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/string-escapes.json
@@ -0,0 +1,34 @@
+{
+    "backspace": {
+        "type": "string",
+        "value": "This string has a \u0008 backspace character."
+    },
+    "tab": {
+        "type": "string",
+        "value": "This string has a \u0009 tab character."
+    },
+    "newline": {
+        "type": "string",
+        "value": "This string has a \u000A new line character."
+    },
+    "formfeed": {
+        "type": "string",
+        "value": "This string has a \u000C form feed character."
+    },
+    "carriage": {
+        "type": "string",
+        "value": "This string has a \u000D carriage return character."
+    },
+    "quote": {
+        "type": "string",
+        "value": "This string has a \u0022 quote character."
+    },
+    "slash": {
+        "type": "string",
+        "value": "This string has a \u002F slash character."
+    },
+    "backslash": {
+        "type": "string",
+        "value": "This string has a \u005C backslash character."
+    }
+}
diff --git a/test/BurntSushi/valid/string-escapes.toml b/test/BurntSushi/valid/string-escapes.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/string-escapes.toml
@@ -0,0 +1,8 @@
+backspace = "This string has a \b backspace character."
+tab = "This string has a \t tab character."
+newline = "This string has a \n new line character."
+formfeed = "This string has a \f form feed character."
+carriage = "This string has a \r carriage return character."
+quote = "This string has a \" quote character."
+slash = "This string has a \/ slash character."
+backslash = "This string has a \\ backslash character."
diff --git a/test/BurntSushi/valid/string-simple.json b/test/BurntSushi/valid/string-simple.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/string-simple.json
@@ -0,0 +1,6 @@
+{
+    "answer": {
+        "type": "string",
+        "value": "You are not drinking enough whisky."
+    }
+}
diff --git a/test/BurntSushi/valid/string-simple.toml b/test/BurntSushi/valid/string-simple.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/string-simple.toml
@@ -0,0 +1,1 @@
+answer = "You are not drinking enough whisky."
diff --git a/test/BurntSushi/valid/string-with-pound.json b/test/BurntSushi/valid/string-with-pound.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/string-with-pound.json
@@ -0,0 +1,7 @@
+{
+    "pound": {"type": "string", "value": "We see no # comments here."},
+    "poundcomment": {
+        "type": "string",
+        "value": "But there are # some comments here."
+    }
+}
diff --git a/test/BurntSushi/valid/string-with-pound.toml b/test/BurntSushi/valid/string-with-pound.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/string-with-pound.toml
@@ -0,0 +1,2 @@
+pound = "We see no # comments here."
+poundcomment = "But there are # some comments here." # Did I # mess you up?
diff --git a/test/BurntSushi/valid/table-array-implicit.json b/test/BurntSushi/valid/table-array-implicit.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-array-implicit.json
@@ -0,0 +1,7 @@
+{
+    "albums": {
+       "songs": [
+           {"name": {"type": "string", "value": "Glory Days"}}
+       ]
+    }
+}
diff --git a/test/BurntSushi/valid/table-array-implicit.toml b/test/BurntSushi/valid/table-array-implicit.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-array-implicit.toml
@@ -0,0 +1,2 @@
+[[albums.songs]]
+name = "Glory Days"
diff --git a/test/BurntSushi/valid/table-array-many.json b/test/BurntSushi/valid/table-array-many.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-array-many.json
@@ -0,0 +1,16 @@
+{
+    "people": [
+        {
+            "first_name": {"type": "string", "value": "Bruce"},
+            "last_name": {"type": "string", "value": "Springsteen"}
+        },
+        {
+            "first_name": {"type": "string", "value": "Eric"},
+            "last_name": {"type": "string", "value": "Clapton"}
+        },
+        {
+            "first_name": {"type": "string", "value": "Bob"},
+            "last_name": {"type": "string", "value": "Seger"}
+        }
+    ]
+}
diff --git a/test/BurntSushi/valid/table-array-many.toml b/test/BurntSushi/valid/table-array-many.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-array-many.toml
@@ -0,0 +1,11 @@
+[[people]]
+first_name = "Bruce"
+last_name = "Springsteen"
+
+[[people]]
+first_name = "Eric"
+last_name = "Clapton"
+
+[[people]]
+first_name = "Bob"
+last_name = "Seger"
diff --git a/test/BurntSushi/valid/table-array-nest.json b/test/BurntSushi/valid/table-array-nest.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-array-nest.json
@@ -0,0 +1,18 @@
+{
+    "albums": [
+        {
+            "name": {"type": "string", "value": "Born to Run"},
+            "songs": [
+                {"name": {"type": "string", "value": "Jungleland"}},
+                {"name": {"type": "string", "value": "Meeting Across the River"}}
+            ]
+        },
+        {
+            "name": {"type": "string", "value": "Born in the USA"},
+            "songs": [
+                {"name": {"type": "string", "value": "Glory Days"}},
+                {"name": {"type": "string", "value": "Dancing in the Dark"}}
+            ]
+        }
+    ]
+}
diff --git a/test/BurntSushi/valid/table-array-nest.toml b/test/BurntSushi/valid/table-array-nest.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-array-nest.toml
@@ -0,0 +1,17 @@
+[[albums]]
+name = "Born to Run"
+
+  [[albums.songs]]
+  name = "Jungleland"
+
+  [[albums.songs]]
+  name = "Meeting Across the River"
+
+[[albums]]
+name = "Born in the USA"
+  
+  [[albums.songs]]
+  name = "Glory Days"
+
+  [[albums.songs]]
+  name = "Dancing in the Dark"
diff --git a/test/BurntSushi/valid/table-array-one.json b/test/BurntSushi/valid/table-array-one.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-array-one.json
@@ -0,0 +1,8 @@
+{
+    "people": [
+        {
+            "first_name": {"type": "string", "value": "Bruce"},
+            "last_name": {"type": "string", "value": "Springsteen"}
+        }
+    ]
+}
diff --git a/test/BurntSushi/valid/table-array-one.toml b/test/BurntSushi/valid/table-array-one.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-array-one.toml
@@ -0,0 +1,3 @@
+[[people]]
+first_name = "Bruce"
+last_name = "Springsteen"
diff --git a/test/BurntSushi/valid/table-empty.json b/test/BurntSushi/valid/table-empty.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-empty.json
@@ -0,0 +1,3 @@
+{
+    "a": {}
+}
diff --git a/test/BurntSushi/valid/table-empty.toml b/test/BurntSushi/valid/table-empty.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-empty.toml
@@ -0,0 +1,1 @@
+[a]
diff --git a/test/BurntSushi/valid/table-sub-empty.json b/test/BurntSushi/valid/table-sub-empty.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-sub-empty.json
@@ -0,0 +1,3 @@
+{
+    "a": { "b": {} }
+}
diff --git a/test/BurntSushi/valid/table-sub-empty.toml b/test/BurntSushi/valid/table-sub-empty.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-sub-empty.toml
@@ -0,0 +1,2 @@
+[a]
+[a.b]
diff --git a/test/BurntSushi/valid/table-whitespace.json b/test/BurntSushi/valid/table-whitespace.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-whitespace.json
@@ -0,0 +1,3 @@
+{
+    "valid key": {}
+}
diff --git a/test/BurntSushi/valid/table-whitespace.toml b/test/BurntSushi/valid/table-whitespace.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-whitespace.toml
@@ -0,0 +1,1 @@
+[valid key]
diff --git a/test/BurntSushi/valid/table-with-pound.json b/test/BurntSushi/valid/table-with-pound.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-with-pound.json
@@ -0,0 +1,5 @@
+{
+    "key#group": {
+        "answer": {"type": "integer", "value": "42"}
+    }
+}
diff --git a/test/BurntSushi/valid/table-with-pound.toml b/test/BurntSushi/valid/table-with-pound.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/table-with-pound.toml
@@ -0,0 +1,2 @@
+[key#group]
+answer = 42
diff --git a/test/BurntSushi/valid/unicode-escape.json b/test/BurntSushi/valid/unicode-escape.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/unicode-escape.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "string", "value": "\u03B4"}
+}
diff --git a/test/BurntSushi/valid/unicode-escape.toml b/test/BurntSushi/valid/unicode-escape.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/unicode-escape.toml
@@ -0,0 +1,1 @@
+answer = "\u03B4"
diff --git a/test/BurntSushi/valid/unicode-literal.json b/test/BurntSushi/valid/unicode-literal.json
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/unicode-literal.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "string", "value": "δ"}
+}
diff --git a/test/BurntSushi/valid/unicode-literal.toml b/test/BurntSushi/valid/unicode-literal.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/valid/unicode-literal.toml
@@ -0,0 +1,1 @@
+answer = "δ"
diff --git a/test/Test.hs b/test/Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Test.hs
@@ -0,0 +1,20 @@
+module Main where
+
+
+import           Test.Tasty            (defaultMain, testGroup)
+
+import qualified BurntSushi
+import           Text.Toml.Parser.Spec
+
+
+main :: IO ()
+main = do
+  parserSpec <- tomlParserSpec
+  bsTests <- BurntSushi.tests
+
+  defaultMain $ testGroup "" $
+    [ parserSpec
+    , bsTests
+      --, quickCheckSuite
+      -- A QuickCheck suite for a parser is possible. The internet knows.
+    ]
diff --git a/test/Text/Toml/Parser/Spec.hs b/test/Text/Toml/Parser/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Text/Toml/Parser/Spec.hs
@@ -0,0 +1,392 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Text.Toml.Parser.Spec where
+
+import           Test.Tasty          (TestTree)
+import           Test.Tasty.Hspec
+
+import           Data.HashMap.Strict (fromList)
+import           Data.Time.Calendar  (Day (..))
+import           Data.Time.Clock     (UTCTime (..))
+
+import           Text.Toml.Parser
+
+
+tomlParserSpec :: IO TestTree
+tomlParserSpec = testSpec "Parser Hspec suite" $ do
+
+  describe "Parser.tomlDoc generic" $ do
+
+    it "should parse empty input" $
+      testParser tomlDoc "" $ fromList []
+
+    it "should parse non-empty tomlDocs that do not end with a newline" $
+      testParser tomlDoc "number = 123" $
+        fromList [("number", NTValue $ VInteger 123)]
+
+    it "should parse when tomlDoc ends in a comment" $
+      testParser tomlDoc "q = 42  # understood?" $
+        fromList [("q", NTValue $ VInteger 42)]
+
+    it "should not parse re-assignment of key" $
+      testParserFails tomlDoc "q=42\nq=42"
+
+    it "should not parse rubbish" $
+      testParserFails tomlDoc "{"
+
+
+  describe "Parser.tomlDoc (named tables)" $ do
+
+    it "should parse simple named table" $
+      testParser tomlDoc "[a]\naa = 108" $
+        fromList [("a", NTable (fromList [("aa", NTValue $ VInteger 108)] ))]
+
+    it "should not parse redefined table header (key already exists at scope)" $
+      testParser tomlDoc "[a]\n[a]" $ fromList [("a", emptyNTable)]
+
+    it "should parse redefinition of implicit key" $
+      testParser tomlDoc "[a.b]\n[a]" $
+        fromList [("a", NTable (fromList [("b", emptyNTable)] ))]
+
+    it "should parse redefinition of implicit key, with table contents" $
+      testParser tomlDoc "[a.b]\nb=3\n[a]\na=4" $
+        fromList [("a", NTable (fromList [("b", NTable (fromList [("b", NTValue $ VInteger 3)])),
+                                          ("a", NTValue $ VInteger 4)]))]
+
+    it "should parse redefinition by implicit table header" $
+      testParser tomlDoc "[a]\n[a.b]" $
+        fromList [("a", NTable (fromList [("b", emptyNTable)] ))]
+
+    it "should not parse redefinition key" $
+      testParserFails tomlDoc "[a]\nb=1\n[a.b]"
+
+
+  describe "Parser.tomlDoc (tables arrays)" $ do
+
+    it "should parse a simple empty table array" $
+      testParser tomlDoc "[[a]]\n[[a]]" $
+        fromList [("a", NTArray [ fromList []
+                                , fromList [] ] )]
+
+    it "should parse a simple table array with content" $
+      testParser tomlDoc "[[a]]\na1=1\n[[a]]\na2=2" $
+        fromList [("a", NTArray [ fromList [("a1", NTValue $ VInteger 1)]
+                                , fromList [("a2", NTValue $ VInteger 2)] ] )]
+
+    it "should not allow a simple table array to be inserted into a non table array" $
+      testParserFails tomlDoc "a = [1,2,3]\n[[a]]"
+
+    it "should parse a simple empty nested table array" $
+      testParser tomlDoc "[[a.b]]\n[[a.b]]" $
+        fromList [("a", NTable (fromList [("b", NTArray [ emptyTable
+                                                        , emptyTable ] )] ) )]
+
+    it "should parse a simple non empty table array" $
+      testParser tomlDoc "[[a.b]]\na1=1\n[[a.b]]\na2=2" $
+        fromList [("a", NTable (fromList [("b", NTArray [ fromList [("a1", NTValue $ VInteger 1)]
+                                                        , fromList [("a2", NTValue $ VInteger 2)]
+                                                        ] )] ) )]
+
+    it "should parse redefined implicit table header" $
+      testParserFails tomlDoc "[[a.b]]\n[[a]]"
+
+    it "should parse redefinition by implicit table header" $
+      testParser tomlDoc "[[a]]\n[[a.b]]" $
+        fromList [("a", NTArray [ fromList [("b", NTArray [ fromList [] ])] ] )]
+
+
+  describe "Parser.tomlDoc (mixed named tables and tables arrays)" $ do
+
+    it "should not parse redefinition of key by table header (table array by table)" $
+      testParserFails tomlDoc "[[a]]\n[a]"
+
+    it "should not parse redefinition of key by table header (table by table array)" $
+      testParserFails tomlDoc "[a]\n[[a]]"
+
+    it "should not parse redefinition implicit table header (table by array)" $
+      testParserFails tomlDoc "[a.b]\n[[a]]"
+
+    it "should parse redefined implicit table header (array by table)" $
+      testParser tomlDoc "[[a.b]]\n[a]" $
+        fromList [("a", NTable (fromList [("b", NTArray [ fromList [] ])] ) )]
+
+    it "should not parse redefined implicit table header (array by table), when keys collide" $
+      testParserFails tomlDoc "[[a.b]]\n[a]\nb=1"
+
+    it "should insert sub-key of regular table in most recently defined table array" $
+      testParser tomlDoc "[[a]]\ni=0\n[[a]]\ni=1\n[a.b]" $
+        fromList [("a", NTArray [ fromList [ ("i", NTValue $ VInteger 0) ]
+                                , fromList [ ("b", NTable  $ fromList [] )
+                                           , ("i", NTValue $ VInteger 1) ]
+                                ] )]
+
+    it "should insert sub-key of table array" $
+      testParser tomlDoc "[a]\n[[a.b]]" $
+        fromList [("a", NTable (fromList [("b", NTArray [fromList []])] ) )]
+
+    it "should insert sub-key (with content) of table array" $
+      testParser tomlDoc "[a]\nq=42\n[[a.b]]\ni=0" $
+        fromList [("a", NTable (fromList [ ("q", NTValue $ VInteger 42),
+                                           ("b", NTArray [
+                                                   fromList [("i", NTValue $ VInteger 0)]
+                                                   ]) ]) )]
+
+  describe "Parser.headerValue" $ do
+
+    it "should parse simple table header" $
+      testParser headerValue "table" ["table"]
+
+    it "should parse simple nested table header" $
+      testParser headerValue "main.sub" ["main", "sub"]
+
+    it "should not parse just a dot (separator)" $
+      testParserFails headerValue "."
+
+    it "should not parse an empty most right name" $
+      testParserFails headerValue  "first."
+
+    it "should not parse an empty most left name" $
+      testParserFails headerValue  ".second"
+
+    it "should not parse an empty middle name" $
+      testParserFails headerValue  "first..second"
+
+
+  describe "Parser.tableHeader" $ do
+
+    it "should not parse an empty table header" $
+      testParserFails tableHeader "[]"
+
+    it "should parse simple table header" $
+      testParser tableHeader "[item]" ["item"]
+
+    it "should parse simple nested table header" $
+      testParser tableHeader "[main.sub]" ["main", "sub"]
+
+
+  describe "Parser.tableArrayHeader" $ do
+
+    it "should not parse an empty table header" $
+      testParserFails tableArrayHeader "[[]]"
+
+    it "should parse simple table array header" $
+      testParser tableArrayHeader "[[item]]" ["item"]
+
+    it "should parse simple nested table array header" $
+      testParser tableArrayHeader "[[main.sub]]" ["main", "sub"]
+
+
+  describe "Parser.assignment" $ do
+
+    it "should parse simple example" $
+      testParser assignment "country = \"\"" ("country", VString "")
+
+    it "should parse without spacing around the assignment operator" $
+      testParser assignment "a=108" ("a", VInteger 108)
+
+    it "should parse when value on next line" $
+      testParser assignment "a =\n108" ("a", VInteger 108)
+
+    it "should parse when assignment operator and value are on the next line" $
+      testParser assignment "a\n= 108" ("a", VInteger 108)
+
+    it "should parse when key, value and assignment operator are on separate lines" $
+      testParser assignment "a\n=\n108" ("a", VInteger 108)
+
+
+  describe "Parser.boolean" $ do
+
+    it "should parse true" $
+      testParser boolean "true" $ VBoolean True
+
+    it "should parse false" $
+      testParser boolean "false" $ VBoolean False
+
+    it "should not parse capitalized variant" $
+      testParserFails boolean "False"
+
+
+  describe "Parser.basicStr" $ do
+
+    it "should parse the common escape sequences in basic strings" $
+      testParser basicStr "\"123\\b\\t\\n\\f\\r\\\"\\/\\\\\"" $ VString "123\b\t\n\f\r\"/\\"
+
+    it "should parse the simple unicode value from the example" $
+      testParser basicStr "\"中国\"" $ VString "中国"
+
+    it "should parse escaped 4 digit unicode values" $
+      testParser assignment "special_k = \"\\u0416\"" ("special_k", VString "Ж")
+
+    it "should parse escaped 8 digit unicode values" $
+      testParser assignment "g_clef = \"\\U0001D11e\"" ("g_clef", VString "𝄞")
+
+    it "should not parse escaped unicode values with missing digits" $
+      testParserFails assignment "g_clef = \"\\U1D11e\""
+
+
+  describe "Parser.multiBasicStr" $ do
+
+    it "should parse simple example" $
+      testParser multiBasicStr "\"\"\"thorrough\"\"\"" $ VString "thorrough"
+
+    it "should parse with newlines" $
+      testParser multiBasicStr "\"\"\"One\nTwo\"\"\"" $ VString "One\nTwo"
+
+    it "should parse with escaped newlines" $
+      testParser multiBasicStr "\"\"\"One\\\nTwo\"\"\"" $ VString "OneTwo"
+
+    it "should parse newlines, ignoring 1 leading newline" $
+      testParser multiBasicStr "\"\"\"\nOne\\\nTwo\"\"\"" $ VString "OneTwo"
+
+    it "should parse with espaced whitespace" $
+      testParser multiBasicStr "\"\"\"\\\n\
+                               \Quick \\\n\
+                               \\\\n\
+                               \Jumped \\\n\
+                               \Lazy\\\n\
+                               \ \"\"\"" $ VString "Quick Jumped Lazy"
+
+
+  describe "Parser.literalStr" $ do
+
+    it "should parse literally" $
+      testParser literalStr "'\"Your\" folder: \\\\User\\new\\tmp\\'" $
+                            VString "\"Your\" folder: \\\\User\\new\\tmp\\"
+
+    it "has no notion of 'escaped single quotes'" $
+      testParserFails tomlDoc "q = 'I don\\'t know.'"  -- string terminates before the "t"
+
+
+  describe "Parser.multiLiteralStr" $ do
+
+    it "should parse literally" $
+      testParser multiLiteralStr
+        "'''\nFirst newline is dropped.\n   Other whitespace,\n  is preserved -- isn't it?'''"
+        $ VString "First newline is dropped.\n   Other whitespace,\n  is preserved -- isn't it?"
+
+
+  describe "Parser.datetime" $ do
+
+    it "should parse a JSON formatted datetime string in zulu timezone" $
+      testParser datetime "1979-05-27T07:32:00Z" $
+        VDatetime $ UTCTime (ModifiedJulianDay 44020) 27120
+
+    it "should not parse only dates" $
+      testParserFails datetime "1979-05-27"
+
+    it "should not parse without the Z" $
+      testParserFails datetime "1979-05-27T07:32:00"
+
+
+  describe "Parser.float" $ do
+
+    it "should parse positive floats" $
+      testParser float "3.14" $ VFloat 3.14
+
+    it "should parse positive floats with plus sign" $
+      testParser float "+3.14" $ VFloat 3.14
+
+    it "should parse negative floats" $
+      testParser float "-0.1" $ VFloat (-0.1)
+
+    it "should parse more or less zero float" $
+      testParser float "0.0" $ VFloat 0.0
+
+    it "should parse 'scientific notation' ('e'-notation)" $
+      testParser float "1.5e6" $ VFloat 1500000.0
+
+    it "should parse 'scientific notation' ('e'-notation) with upper case E" $
+      testParser float "1E0" $ VFloat 1.0
+
+    it "should not accept floats starting with a dot" $
+      testParserFails float ".5"
+
+    it "should not accept floats without any decimals" $
+      testParserFails float "5."
+
+
+  describe "Parser.integer" $ do
+
+    it "should parse positive integers" $
+      testParser integer "108" $ VInteger 108
+
+    it "should parse negative integers" $
+      testParser integer "-1" $ VInteger (-1)
+
+    it "should parse zero" $
+      testParser integer "0" $ VInteger 0
+
+    it "should parse integers prefixed with a plus" $
+      testParser integer "+42" $ VInteger 42
+
+
+  describe "Parser.tomlDoc arrays" $ do
+
+    it "should parse an empty array" $
+      testParser array "[]" $ VArray []
+
+    it "should parse an empty array with whitespace" $
+      testParser array "[ ]" $ VArray []
+
+    it "should not parse an empty array with only a terminating comma" $
+      testParserFails array "[,]"
+
+    it "should parse an empty array of empty arrays" $
+      testParser array "[[],[]]" $ VArray [ VArray [], VArray [] ]
+
+    it "should parse an empty array of empty arrays with whitespace" $
+      testParser array "[ \n[ ]\n ,\n [ \n ] ,\n ]" $ VArray [ VArray [], VArray [] ]
+
+    it "should parse nested arrays" $
+      testParser assignment "d = [ ['gamma', 'delta'], [1, 2] ]"
+              $ ("d", VArray [ VArray [ VString "gamma"
+                                      , VString "delta" ]
+                             , VArray [ VInteger 1
+                                      , VInteger 2 ] ])
+
+    it "should allow linebreaks in an array" $
+      testParser assignment "hosts = [\n'alpha',\n'omega'\n]"
+        $ ("hosts", VArray [VString "alpha", VString "omega"])
+
+    it "should allow some linebreaks in an array" $
+      testParser assignment "hosts = ['alpha' ,\n'omega']"
+        $ ("hosts", VArray [VString "alpha", VString "omega"])
+
+    it "should allow linebreaks in an array, with comments" $
+      testParser assignment "hosts = [\n\
+                            \'alpha',  # the first\n\
+                            \'omega'   # the last\n\
+                            \]"
+        $ ("hosts", VArray [VString "alpha", VString "omega"])
+
+    it "should allow linebreaks in an array, with comments, and terminating comma" $
+      testParser assignment "hosts = [\n\
+                            \'alpha',  # the first\n\
+                            \'omega',  # the last\n\
+                            \]"
+        $ ("hosts", VArray [VString "alpha", VString "omega"])
+
+    it "inside an array, all element should be of the same type" $
+      testParserFails array "[1, 2.0]"
+
+    it "inside an array of arrays, this inner arrays may contain values of different types" $
+      testParser array "[[1], [2.0], ['a']]" $
+        VArray [ VArray [VInteger 1], VArray [VFloat 2.0], VArray [VString "a"] ]
+
+    it "all string variants are of the same type of the same type" $
+      testParser assignment "data = [\"a\", \"\"\"b\"\"\", 'c', '''d''']" $
+                            ("data", VArray [ VString "a", VString "b",
+                                              VString "c", VString "d" ])
+
+    it "should parse terminating commas in arrays" $
+      testParser array "[1, 2, ]" $ VArray [ VInteger 1, VInteger 2 ]
+
+    it "should parse terminating commas in arrays(2)" $
+      testParser array "[1,2,]" $ VArray [ VInteger 1, VInteger 2 ]
+
+
+  where
+    testParser p str success = case parseOnly p str of Left  _ -> False
+                                                       Right x -> x == success
+    testParserFails p str    = case parseOnly p str of Left  _ -> True
+                                                       Right _ -> False
