diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -4,6 +4,13 @@
 #### dev
 * ...
 
+#### 0.2.0.0
+* Compatible with TOML 0.4.0
+* Improve test suite (all test now pass -- thanks @HuwCampbell)
+* Slight API breakage (therefore major version bump)
+* Use Parsec's parser state to track explicitness of table definitions (thanks @HuwCampbell)
+* Clean up docs and code
+
 #### 0.1.0.3
 * GHC 7.10 compatibility fix (thanks @erebe)
 * Allow time >= 1.5.0, by using some CPP trickery
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,6 +1,15 @@
 htoml
 =====
 
+[![Build Status](https://travis-ci.org/cies/htoml.svg?branch=master)](https://travis-ci.org/cies/htoml)
+[![Latest version on Hackage](https://img.shields.io/hackage/v/htoml.svg)](https://hackage.haskell.org/package/html)
+[![Dependencies of latest version on Hackage](https://img.shields.io/hackage-deps/v/htoml.svg)](https://hackage.haskell.org/package/html)
+
+[![htoml on Stackage LTS 2](http://stackage.org/package/htoml/badge/lts-2)](http://stackage.org/lts-2/package/htoml)
+[![htoml on Stackage LTS 3](http://stackage.org/package/htoml/badge/lts-3)](http://stackage.org/lts-3/package/htoml)
+[![htoml on Stackage Nightly](http://stackage.org/package/htoml/badge/nightly)](http://stackage.org/nightly/package/htoml)
+
+
 A [TOML](https://github.com/mojombo/toml) parser library in
 [Haskell](http://haskell-lang.org).
 
@@ -8,14 +17,14 @@
 [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.
+[INI](http://en.wikipedia.org/wiki/INI_file) formats mainly for the purpose of
+configuration files. Many will find that XML and YAML are too heavy for
+the purpose of configuration files prupose while INI 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).
+[v0.4.0](https://github.com/toml-lang/toml/releases/tag/v0.4.0).
 
 The documentation for this package may (or may not) be found on
 [Hackage](https://hackage.haskell.org/package/htoml).
@@ -23,39 +32,55 @@
 
 ### Quick start
 
-Installing `htoml` is easy.
+Installing `htoml` is easy. Either by using
+[Stack](http://haskellstack.org) (recommended):
 
+    stack install htoml
+
+Or by using Cabal:
+
     cabal install htoml
 
 In order to make your project depend on it you can add it as a
-dependency in your project's cabal file.
+dependency in your project's `.cabal` file, and since it is not
+yet on [Stackage](https://www.stackage.org/) you will also have
+to add it to the `extra-deps` section of your `stack.yaml` file
+when using Stack.
 
-To quickly show some features of `htoml` we start `GHCi` from the
-root of the repository so it picks up configuration from the
-`.ghci` file that lives there.
+To quickly show some features of `htoml` we use Stack to start a
+GHCi-based REPL. It picks up configuration from the `.ghci` file
+in the root of the repository.
 
     git clone https://github.com/cies/htoml.git
     cd htoml
-    cabal sandbox init  ;# initialize a cabal sandbox
-    cabal install       ;# install the dependencies and build all
-    cabal repl          ;# starts a sandbox-aware GHCi
+    stack init
+    stack --install-ghc ghci
 
-We can immediately start exploring from the `GHCi` prompt.
+Add a `--resolver` flag to the `stack init` command to specify
+a specific package snapshot, e.g.: `--resolver lts-4.1`.
 
+In case you have missing dependencies (possibly `file-embed`),
+they can be added to the `extra-deps` in `stack.yaml`
+automatically with:
+
+    stack solver --update-config
+
+We can now start exploring `htoml` from a GHCi REPL.
+
     > txt <- readFile "benchmarks/example.toml"
     > let r = parseTomlDoc "" txt
     > r
-    Right (fromList [("database",NTable (fromList [("enabled",NTValue (VBoolean True) [...]
+    Right (fromList [("database",VTable (fromList [("enabled",VBoolean True),("po [...]
 
     > let Right toml = r
     > toJSON toml
-    Object (fromList [("database",Object (fromList [("enabled",Bool True) [...]
+    Object (fromList [("database",Object (fromList [("enabled",Bool True),("po [...]
 
-    > let Left error = parseTomlDoc "" "== invalid toml =="
-    > e
+    > let Left err = parseTomlDoc "" "== invalid toml =="
+    > err
     (line 1, column 1):
     unexpected '='
-    expecting "#", "[" or end of input
+    expecting "#", "\n", "\r\n", letter or digit, "_", "-", "\"", "'", "[" or end of inputr
 
 Notice that some outputs are truncated, indicated by `[...]`.
 
@@ -69,7 +94,7 @@
 For example let's consider the following `parseResult`:
 
 ```haskell
-Right (fromList [("server",NTable (fromList [("enabled",NTValue (VBoolean True))] ) )] )
+Right (fromList [("server",VTable (fromList [("enabled",VBoolean True)] ) )] )
 ```
 
 Which could be pattern matched with:
@@ -78,7 +103,7 @@
 case parseResult of
   Left  _ -> "Could not parse file"
   Right m -> case m ! "server" of
-    NTable mm -> case mm ! "enabled" of
+    VTable mm -> case mm ! "enabled" of
       VBoolean b -> "Server is " ++ (if b then "enabled" else "disabled")
       _ -> "Could not parse server status (Boolean)"
     _ -> "TOML file does not contain the 'server' key"
@@ -91,10 +116,19 @@
 approach.
 
 Other ways to pull data from a parsed TOML document will most likely
-exist; maybe the `lens` library can give great results in some cases.
-But I have no experience with them.
+exist; possible using the `lens` library as
+[documented here](https://github.com/cies/htoml/issues/8).
 
 
+### Compatibility
+
+Currently we are testing against several versions of GHC with
+[Travis CI](https://travis-ci.org/cies/htoml) as defined in the `env` section of our
+[`.travis.yml`](https://github.com/cies/htoml/blob/master/.travis.yml).
+`lts-2` implies GHC 7.8.4, `lts-3` implies GHC 7.10.2, `lts-4`/`lts-5`
+imply GHC 7.10.3, and `nightly` is build with a regularly updated version of GHC.
+
+
 ### Version contraints of `htoml`'s dependencies
 
 If you encounter any problems because `htoml`'s dependecies are
@@ -108,24 +142,26 @@
 
 ### 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.
+Tests are build and run with:
 
-With `cabal build` both of these suites are build as executables and
-put somewhere in `dist/`. Passing `--help` to them will reveal their
-options.
+    stack test
 
 [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.
+its Github repository.
 
+The benchmarks, that use the amazing [`criterion`](http://www.serpentine.com/criterion)
+library, are build and run with:
 
+    stack build :benchmarks
+
+
 ### 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
+This is one of the first Haskell libraries I wrote, feedback is
 much appreciated.
 
 
@@ -145,20 +181,26 @@
 ### Todo
 
 * Release a stable 1.0 release and submit it to [Stackage](http://stackage.org)
+* Once 1.0 is out, keep a compatibility chart showing which versions of htoml are
+  compatible with which versions of the TOML spec
 * More documentation
-* Make all tests pass (currently some more obscure corner cases don't pass)
 * 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
+* Try using `Vector` instead of `List` (measure performance increase with the benchmarks)
+* See how lenses may (or may not) fit into this package, or an additional package
+* Consider moving to [one of the more modern parser combinators](https://www.reddit.com/r/haskell/comments/46u45o/what_is_the_current_state_of_parser_libraries_in)
+  in Haskell (`megaparsec` maybe?) -- possibly wait until a clear winner shows
 
 
-### Acknoledgements
+### Acknowledgements
 
 Originally this project started off by improving the `toml` package by
 Spiros Eliopoulos.
 
+[HuwCampbell](https://github.com/HuwCampbell) helped a lot by making tests
+pass and implementing "explicitness tracking" in Parsec's parser state.
 
+
 ### Copyright and licensing
 
 This package includes BurntSushi's language agnostic
@@ -169,5 +211,5 @@
 from Tom Preston-Werner's TOML spec which is MIT licensed.
 
 For all other files in this project the copyrights are specified in the
-`htoml.cabal` file, and are distributed under the BSD3 license as found
+`htoml.cabal` file, they are distributed under the BSD3 license as found
 in the `LICENSE` file.
diff --git a/htoml.cabal b/htoml.cabal
--- a/htoml.cabal
+++ b/htoml.cabal
@@ -1,16 +1,16 @@
 name:                     htoml
-version:                  0.1.0.3
+version:                  0.2.0.0
 synopsis:                 Parser for TOML files
 description:              TOML is an obvious and minimal format for config files.
                           .
                           This package provides a TOML parser,
-                          build with the Parsec library, and providing a JSON
+                          build with the Parsec library. It exposes 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
-copyright:                (c) 2013-2014 Cies Breijs
+copyright:                (c) 2013-2016 Cies Breijs
 author:                   Cies Breijs
 maintainer:               Cies Breijs <cies % kde ! nl>
 category:                 Data, Text, Parser, Configuration, JSON, Language
@@ -64,7 +64,7 @@
                         , text
                         , time
                         , bytestring             >= 0.9
-                        , file-embed             >= 0.0.5
+                        , file-embed             >= 0.0.10
                         , Cabal                  >= 1.16.0
                         , tasty                  >= 0.10
                         , tasty-hspec            >= 0.2
@@ -80,6 +80,7 @@
   type:                   exitcode-stdio-1.0
   default-language:        Haskell2010
   build-depends:          base
+                        , aeson
                         , parsec
                         , containers
                         , bytestring
@@ -94,6 +95,7 @@
                         , tasty
                         , tasty-hspec
                         , tasty-hunit
+                        , aeson
 
 benchmark benchmarks
   hs-source-dirs:         benchmarks .
diff --git a/src/Text/Toml.hs b/src/Text/Toml.hs
--- a/src/Text/Toml.hs
+++ b/src/Text/Toml.hs
@@ -1,8 +1,7 @@
 module Text.Toml where
 
-import           Prelude          hiding (readFile)
-
 import           Data.Text        (Text)
+import           Data.Set         (empty)
 import           Text.Parsec
 
 import           Text.Toml.Parser
@@ -10,6 +9,6 @@
 
 -- | 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.
+-- of the document as a 'Table'.
 parseTomlDoc :: String -> Text -> Either ParseError Table
-parseTomlDoc inputName input = parse tomlDoc inputName input
+parseTomlDoc inputName input = runParser tomlDoc empty inputName input
diff --git a/src/Text/Toml/Parser.hs b/src/Text/Toml/Parser.hs
--- a/src/Text/Toml/Parser.hs
+++ b/src/Text/Toml/Parser.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE FlexibleContexts  #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes        #-}
 {-# LANGUAGE CPP               #-}
 
 module Text.Toml.Parser
@@ -7,10 +8,9 @@
   , module Text.Toml.Types
   ) where
 
-
-import           Prelude             hiding (concat, takeWhile)
-
 import           Control.Applicative hiding (many, optional, (<|>))
+import           Control.Monad
+
 import qualified Data.HashMap.Strict as M
 import qualified Data.List           as L
 import qualified Data.Set            as S
@@ -26,47 +26,61 @@
 
 import           Numeric             (readHex)
 import           Text.Parsec
-import           Text.Parsec.Text
 
 import           Text.Toml.Types
 
+-- Imported as last to fix redundancy warning
+import           Prelude             hiding (concat, takeWhile)
 
 
+-- | Our very own Parser type.
+type Parser a = forall s. Parsec Text s a
+
+
 -- | Convenience function for the test suite and GHCI.
-parseOnly :: Parser a -> Text -> Either ParseError a
-parseOnly p str = parse (p <* eof) "test" str
+parseOnly :: Parsec Text (S.Set [Text]) a -> Text -> Either ParseError a
+parseOnly p str = runParser (p <* eof)  S.empty "test" str
 
 
 -- | Parses a complete document formatted according to the TOML spec.
-tomlDoc :: Parser Table
+tomlDoc :: Parsec Text (S.Set [Text]) 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
-
+    -- Ensure the input is completely consumed
+    eof
+    -- Load each named section into the top table
+    foldM (flip (insert Explicit)) topTable namedSections
 
 -- | 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
+    case maybeDupe (map fst pairs) of
       Just k  -> fail $ "Cannot redefine key " ++ (unpack k)
-      Nothing -> return $ M.fromList (map (\(k, v) -> (k, NTValue v)) pairs)
+      Nothing -> return $ M.fromList pairs
+
+-- | Parses an inline table of key-value pairs.
+inlineTable :: Parser Node
+inlineTable = do
+    pairs <- between (char '{') (char '}') (skipSpaces *> separatedValues <* skipSpaces)
+    case maybeDupe (map fst pairs) of
+      Just k  -> fail $ "Cannot redefine key " ++ (unpack k)
+      Nothing -> return $ VTable $ M.fromList 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)
+    skipSpaces      = many (satisfy isSpc)
+    separatedValues = sepBy (skipSpaces *> assignment <* skipSpaces) comma
+    comma           = skipSpaces >> char ',' >> skipSpaces
 
+-- | Find dupes, if any.
+maybeDupe :: Ord a => [a] -> Maybe a
+maybeDupe xx = dup xx S.empty
+  where
+    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.
@@ -76,8 +90,8 @@
     skipBlanks
     tbl <- table
     skipBlanks
-    return $ case eitherHdr of Left  ns -> (ns, NTable   tbl )
-                               Right ns -> (ns, NTArray [tbl])
+    return $ case eitherHdr of Left  ns -> (ns, VTable   tbl )
+                               Right ns -> (ns, VTArray [tbl])
 
 
 -- | Parses a table header.
@@ -94,40 +108,38 @@
 
 -- | Parses the value of any header (names separated by dots), into a list of 'Text'.
 headerValue :: Parser [Text]
-headerValue = (pack <$> many1 headerNameChar) `sepBy1` (char '.')
+headerValue = ((pack <$> many1 keyChar) <|> anyStr') `sepBy1` (char '.')
   where
-    headerNameChar = satisfy (\c -> c /= ' ' && c /= '\t' && c /= '\n' &&
-                                    c /= '[' && c /= ']'  && c /= '.'  && c /= '#')
-
+    keyChar = alphaNum <|> char '_' <|> char '-'
 
 -- | Parses a key-value assignment.
-assignment :: Parser (Text, TValue)
+assignment :: Parser (Text, Node)
 assignment = do
-    k <- pack <$> many1 keyChar
-    skipBlanks >> char '=' >> skipBlanks
+    k <- (pack <$> many1 keyChar) <|> anyStr'
+    many (satisfy isSpc) >> 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 /= '[')
+    keyChar = alphaNum <|> char '_' <|> char '-'
 
 
 -- | 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")
+value :: Parser Node
+value = (try array       <?> "array")
+    <|> (try boolean     <?> "boolean")
+    <|> (try anyStr      <?> "string")
+    <|> (try datetime    <?> "datetime")
+    <|> (try float       <?> "float")
+    <|> (try integer     <?> "integer")
+    <|> (try inlineTable <?> "inline table")
 
 
 --
 -- | * Toml value parsers
 --
 
-array :: Parser TValue
+array :: Parser Node
 array = (try (arrayOf array)    <?> "array of arrays")
     <|> (try (arrayOf boolean)  <?> "array of booleans")
     <|> (try (arrayOf anyStr)   <?> "array of strings")
@@ -136,43 +148,46 @@
     <|> (try (arrayOf integer)  <?> "array of integers")
 
 
-boolean :: Parser TValue
+boolean :: Parser Node
 boolean = VBoolean <$> ( (try . string $ "true")  *> return True  <|>
                          (try . string $ "false") *> return False )
 
 
-anyStr :: Parser TValue
-anyStr = try multiBasicStr <|> try basicStr <|> try multiLiteralStr <|> try literalStr
+anyStr :: Parser Node
+anyStr = VString <$> anyStr'
 
+anyStr' :: Parser Text
+anyStr' = try multiBasicStr <|> try basicStr <|> try multiLiteralStr <|> try literalStr
 
-basicStr :: Parser TValue
-basicStr = VString <$> between dQuote dQuote (fmap pack $ many strChar)
+
+basicStr :: Parser Text
+basicStr = 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))
+multiBasicStr :: Parser Text
+multiBasicStr = (openDQuote3 *> escWhiteSpc *> (pack <$> manyTill strChar (try 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
+    strChar     = (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 (/= '\'')))
+literalStr :: Parser Text
+literalStr = between sQuote sQuote (pack <$> many (satisfy (/= '\'')))
   where
     sQuote = char '\''
 
 
-multiLiteralStr :: Parser TValue
-multiLiteralStr = VString <$> (openSQuote3 *> (fmap pack $ manyTill anyChar sQuote3))
+multiLiteralStr :: Parser Text
+multiLiteralStr = (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
@@ -180,9 +195,9 @@
     sQuote3     = try . count 3 . char $ '\''
 
 
-datetime :: Parser TValue
+datetime :: Parser Node
 datetime = do
-    d <- manyTill anyChar (try $ char 'Z')
+    d <- try $ manyTill anyChar (char 'Z')
 #if MIN_VERSION_time(1,5,0)
     let  mt = parseTimeM True defaultTimeLocale (iso8601DateFormat $ Just "%X") d
 #else
@@ -193,35 +208,35 @@
 
 
 -- | Attoparsec 'double' parses scientific "e" notation; reimplement according to Toml spec.
-float :: Parser TValue
+float :: Parser Node
 float = VFloat <$> do
-    n <- intStr
-    _ <- char '.'
-    d <- uintStr
+    n <- intStr <* lookAhead (satisfy (\c -> c == '.' || c == 'e' || c == 'E'))
+    d <- try (satisfy (== '.') *> uintStr) <|> return "0"
     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
+    uintStr = (:) <$> digit <*> many (optional (char '_') *> digit)
     intStr  = do s <- sign
                  u <- uintStr
                  return . L.concat $ [s, u]
 
 
-integer :: Parser TValue
-integer = VInteger <$> (signed $ read <$> (many1 digit))
-
-
+integer :: Parser Node
+integer = VInteger <$> (signed $ read <$> uintStr)
+  where
+    uintStr :: Parser [Char]
+    uintStr = (:) <$> digit <*> many (optional (char '_') *> digit)
 
 --
 -- * Utility functions
 --
 
 -- | Parses the elements of an array, while restricting them to a certain type.
-arrayOf :: Parser TValue -> Parser TValue
+arrayOf :: Parser Node -> Parser Node
 arrayOf p = VArray <$> between (char '[') (char ']') (skipBlanks *> separatedValues)
   where
-    separatedValues = sepEndBy (skipBlanks *> p <* skipBlanks) comma <* skipBlanks
+    separatedValues = sepEndBy (skipBlanks *> try p <* skipBlanks) comma <* skipBlanks
     comma           = skipBlanks >> char ',' >> skipBlanks
 
 
@@ -275,4 +290,4 @@
 
 -- | 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 ()
+eol = (string "\n" <|> string "\r\n") >> return ()
diff --git a/src/Text/Toml/Types.hs b/src/Text/Toml/Types.hs
--- a/src/Text/Toml/Types.hs
+++ b/src/Text/Toml/Types.hs
@@ -1,12 +1,24 @@
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
 
-module Text.Toml.Types where
+module Text.Toml.Types (
+    Table
+  , emptyTable
+  , Node (..)
+  , Explicitness (..)
+  , isExplicit
+  , insert
+  , ToBsJSON (..)
+  ) where
 
+import           Control.Monad       (when)
+import           Text.Parsec
 import           Data.Aeson.Types
 import qualified Data.HashMap.Strict as M
 import           Data.Int            (Int64)
 import           Data.List           (intersect)
+import           Data.Set (Set)
+import qualified Data.Set            as S
 import           Data.Text           (Text)
 import qualified Data.Text           as T
 import           Data.Time.Clock     (UTCTime)
@@ -17,105 +29,129 @@
 -- | 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
 
+-- | A 'Node' may contain any type of value that can put in a 'VArray'.
+data Node = VTable    Table
+          | VTArray   [Table]
+          | VString   !Text
+          | VInteger  !Int64
+          | VFloat    !Double
+          | VBoolean  !Bool
+          | VDatetime !UTCTime
+          | VArray    [Node]
+  deriving (Eq, Show)
 
--- | Contruct an empty 'NTable'.
-emptyNTable :: Node
-emptyNTable = NTable M.empty
+-- | To mark whether or not a 'Table' has been explicitly defined.
+-- See: https://github.com/toml-lang/toml/issues/376
+data Explicitness = Explicit | Implicit
 
+-- | Convenience function to get a boolean value.
+isExplicit :: Explicitness -> Bool
+isExplicit Explicit = True
+isExplicit Implicit = False
 
--- | 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
+
+-- | Inserts a table, 'Table', with the namespaced name, '[Text]', (which
+-- may be part of a table array) into a 'Table'.
+-- It may result in an error in the 'ParsecT' monad for redefinitions.
+insert :: Explicitness -> ([Text], Node) -> Table -> Parsec Text (Set [Text]) Table
+insert _ ([], _) _ = parserFail "FATAL: Cannot call 'insert' without a name."
+insert ex ([name], node) ttbl =
+    -- In case 'name' is final (a top-level name)
     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
-        _         -> commonInsertError node [name]
-      Just (NTArray a)  -> case node of
-        (NTArray na) -> Right $ M.insert name (NTArray $ a ++ na) ttbl
-        _         -> commonInsertError node [name]
-      Just _            -> commonInsertError node [name]
-insert (fullName@(name:ns), node) ttbl =
-    -- In case 'name' is not final, but a sub-name
+      Nothing -> do when (isExplicit ex) $ updateExState [name] node
+                    return $ M.insert name node ttbl
+      Just (VTable t) -> case node of
+          (VTable nt) -> case merge t nt of
+                  Left ds -> nameInsertError ds name
+                  Right r -> do when (isExplicit ex) $
+                                  updateExStateOrError [name] node
+                                return $ M.insert name (VTable r) ttbl
+          _ -> commonInsertError node [name]
+      Just (VTArray a) -> case node of
+          (VTArray na) -> return $ M.insert name (VTArray $ a ++ na) ttbl
+          _ -> commonInsertError node [name]
+      Just _ -> commonInsertError node [name]
+insert ex (fullName@(name:ns), node) ttbl =
+    -- In case 'name' is not final (not a top-level 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
+      Nothing -> do
+          r <- insert Implicit (ns, node) emptyTable
+          when (isExplicit ex) $ updateExState fullName node
+          return $ M.insert name (VTable r) ttbl
+      Just (VTable t) -> do
+          r <- insert Implicit (ns, node) t
+          when (isExplicit ex) $ updateExStateOrError fullName node
+          return $ M.insert name (VTable r) ttbl
+      Just (VTArray []) ->
+          parserFail "FATAL: Call to 'insert' found impossibly empty VArray."
+      Just (VTArray a) -> do
+          r <- insert Implicit (ns, node) (last a)
+          return $ M.insert name (VTArray $ (init a) ++ [r]) 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 ('Left' will contain 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
+merge existing new = case M.keys existing `intersect` M.keys new of
                        [] -> Right $ M.union existing new
                        ds -> Left  $ ds
 
+-- TOML tables maybe redefined when first definition was implicit.
+-- For instance a top-level table `a` can implicitly defined by defining a non top-level
+-- table `b` under it (namely with `[a.b]`). Once the table `a` is subsequently defined
+-- explicitly (namely with `[a]`), it is then not possible to (re-)define it again.
+-- A parser state of all explicitly defined tables is maintained, which allows
+-- raising errors for illegal redefinitions of such.
+updateExStateOrError :: [Text] -> Node -> Parsec Text (Set [Text]) ()
+updateExStateOrError name node@(VTable _) = do
+    explicitlyDefinedNames <- getState
+    when (S.member name explicitlyDefinedNames) $ tableClashError name
+    updateExState name node
+updateExStateOrError _ _ = return ()
 
--- | Convenience function to construct a common error message for the 'insert' function.
-commonInsertError :: Node -> [Text] -> Either Text Table
-commonInsertError what name = Left . T.concat $ case what of
-    NTValue _ -> ["Cannot insert a value '", n, "'."]
-    _         -> ["Cannot insert ", w, " '", n, "' as key already exists."]
+-- | Like 'updateExStateOrError' but does not raise errors. Only use this when sure
+-- that redefinitions cannot occur.
+updateExState :: [Text] -> Node -> Parsec Text (S.Set [Text]) ()
+updateExState name (VTable _) = modifyState $ S.insert name
+updateExState _ _ = return ()
+
+
+-- * Parse errors resulting from invalid TOML
+
+-- | Key(s) redefintion error.
+nameInsertError :: [Text] -> Text -> Parsec Text (Set [Text]) a
+nameInsertError ns name = parserFail . T.unpack $ T.concat
+    [ "Cannot redefine key(s) (", T.intercalate ", " ns
+    , "), from table named '", name, "'." ]
+
+-- | Table redefinition error.
+tableClashError :: [Text] -> Parsec Text (Set [Text]) a
+tableClashError name = parserFail . T.unpack $ T.concat
+    [ "Cannot redefine table ('", T.intercalate ", " name , "'." ]
+
+-- | Common redefinition error.
+commonInsertError :: Node -> [Text] -> Parsec Text (Set [Text]) a
+commonInsertError what name = parserFail . concat $
+    [ "Cannot insert ", w, " '", n, "' as key already exists." ]
   where
-    n = T.intercalate "." name
-    w = case what of (NTable _) -> "tables"
+    n = T.unpack $ T.intercalate "." name
+    w = case what of (VTable _) -> "tables"
                      _          -> "array of tables"
 
 
-
 -- * 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 (VTable v)    = toJSON v
+  toJSON (VTArray v)   = toJSON v
   toJSON (VString v)   = toJSON v
   toJSON (VInteger v)  = toJSON v
   toJSON (VFloat v)    = toJSON v
@@ -147,21 +183,14 @@
   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
+instance ToBsJSON Node where
+  toBsJSON (VTable v)    = toBsJSON v
+  toBsJSON (VTArray v)   = toBsJSON v
   toBsJSON (VString v)   = object [ "type"  .= toJSON ("string" :: String)
                                   , "value" .= toJSON v ]
   toBsJSON (VInteger v)  = object [ "type"  .= toJSON ("integer" :: String)
@@ -172,9 +201,9 @@
                                   , "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"
+                                                           z = take (length s - 4)  s ++ "Z"
                                                            d = take (length z - 10) z
-                                                           t = drop (length z - 9) 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
--- a/test/BurntSushi.hs
+++ b/test/BurntSushi.hs
@@ -18,7 +18,7 @@
 
 
 allFiles :: [(FilePath, B.ByteString)]
-allFiles = $(embedDir "test/BurntSushi")
+allFiles = $(makeRelativeToProject "test/BurntSushi" >>= embedDir)
 
 
 validPairs :: [(String, (B.ByteString, B.ByteString))]
@@ -59,4 +59,4 @@
     assertParseFailure f tomlBS =
       case parseTomlDoc "test" (decodeUtf8 tomlBS) of
         Left _ -> return ()
-        Right _ -> assertFailure $ "Parser accepted invalid TOML file: " ++ f ++ ".toml"
+        Right _ -> assertFailure $ "Parser accepted invalid TOML file: " ++ f
diff --git a/test/BurntSushi/invalid/key-space.toml b/test/BurntSushi/invalid/key-space.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/key-space.toml
@@ -0,0 +1,1 @@
+a b = 1
diff --git a/test/BurntSushi/invalid/table-whitespace.toml b/test/BurntSushi/invalid/table-whitespace.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/table-whitespace.toml
@@ -0,0 +1,1 @@
+[invalid key]
diff --git a/test/BurntSushi/invalid/table-with-pound.toml b/test/BurntSushi/invalid/table-with-pound.toml
new file mode 100644
--- /dev/null
+++ b/test/BurntSushi/invalid/table-with-pound.toml
@@ -0,0 +1,2 @@
+[key#group]
+answer = 42
diff --git a/test/BurntSushi/valid/key-space.toml b/test/BurntSushi/valid/key-space.toml
--- a/test/BurntSushi/valid/key-space.toml
+++ b/test/BurntSushi/valid/key-space.toml
@@ -1,1 +1,1 @@
-   a b    = 1
+"a b" = 1
diff --git a/test/BurntSushi/valid/key-special-chars.json b/test/BurntSushi/valid/key-special-chars.json
--- a/test/BurntSushi/valid/key-special-chars.json
+++ b/test/BurntSushi/valid/key-special-chars.json
@@ -1,5 +1,5 @@
 {
-    "~!@$^&*()_+-`1234567890[]\\|/?><.,;:'": {
+    "~!@$^&*()_+-`1234567890[]|/?><.,;:'": {
         "type": "integer", "value": "1"
     }
 }
diff --git a/test/BurntSushi/valid/key-special-chars.toml b/test/BurntSushi/valid/key-special-chars.toml
--- a/test/BurntSushi/valid/key-special-chars.toml
+++ b/test/BurntSushi/valid/key-special-chars.toml
@@ -1,1 +1,1 @@
-~!@$^&*()_+-`1234567890[]\|/?><.,;:' = 1
+"~!@$^&*()_+-`1234567890[]|/?><.,;:'" = 1
diff --git a/test/BurntSushi/valid/long-integer.json b/test/BurntSushi/valid/long-integer.json
--- a/test/BurntSushi/valid/long-integer.json
+++ b/test/BurntSushi/valid/long-integer.json
@@ -1,4 +1,4 @@
 {
-    "answer": {"type": "integer", "value": "9223372036854776000"},
-    "neganswer": {"type": "integer", "value": "-9223372036854776000"}
+    "answer": {"type": "integer", "value": "9223372036854775807"},
+    "neganswer": {"type": "integer", "value": "-9223372036854775808"}
 }
diff --git a/test/BurntSushi/valid/long-integer.toml b/test/BurntSushi/valid/long-integer.toml
--- a/test/BurntSushi/valid/long-integer.toml
+++ b/test/BurntSushi/valid/long-integer.toml
@@ -1,2 +1,2 @@
-answer = 9223372036854776000
-neganswer = -9223372036854776000
+answer = 9223372036854775807
+neganswer = -9223372036854775808
diff --git a/test/BurntSushi/valid/multiline-string.json b/test/BurntSushi/valid/multiline-string.json
--- a/test/BurntSushi/valid/multiline-string.json
+++ b/test/BurntSushi/valid/multiline-string.json
@@ -1,29 +1,29 @@
 {
-    "multiline empty one": {
+    "multiline_empty_one": {
         "type": "string",
         "value": ""
     },
-    "multiline empty two": {
+    "multiline_empty_two": {
         "type": "string",
         "value": ""
     },
-    "multiline empty three": {
+    "multiline_empty_three": {
         "type": "string",
         "value": ""
     },
-    "multiline empty four": {
+    "multiline_empty_four": {
         "type": "string",
         "value": ""
     },
-    "equivalent one": {
+    "equivalent_one": {
         "type": "string",
         "value": "The quick brown fox jumps over the lazy dog."
     },
-    "equivalent two": {
+    "equivalent_two": {
         "type": "string",
         "value": "The quick brown fox jumps over the lazy dog."
     },
-    "equivalent three": {
+    "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
--- a/test/BurntSushi/valid/multiline-string.toml
+++ b/test/BurntSushi/valid/multiline-string.toml
@@ -1,22 +1,22 @@
-multiline empty one = """"""
-multiline empty two = """
+multiline_empty_one = """"""
+multiline_empty_two = """
 """
-multiline empty three = """\
+multiline_empty_three = """\
     """
-multiline empty four = """\
+multiline_empty_four = """\
    \
    \
    """
 
-equivalent one = "The quick brown fox jumps over the lazy dog."
-equivalent two = """
+equivalent_one = "The quick brown fox jumps over the lazy dog."
+equivalent_two = """
 The quick brown \
 
 
   fox jumps over \
     the lazy dog."""
 
-equivalent three = """\
+equivalent_three = """\
        The quick brown \
        fox jumps over \
        the lazy dog.\
diff --git a/test/BurntSushi/valid/string-escapes.json b/test/BurntSushi/valid/string-escapes.json
--- a/test/BurntSushi/valid/string-escapes.json
+++ b/test/BurntSushi/valid/string-escapes.json
@@ -23,12 +23,24 @@
         "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."
+    },
+    "notunicode1": {
+        "type": "string",
+        "value": "This string does not have a unicode \\u escape."
+    },
+    "notunicode2": {
+        "type": "string",
+        "value": "This string does not have a unicode \u005Cu escape."
+    },
+    "notunicode3": {
+        "type": "string",
+        "value": "This string does not have a unicode \\u0075 escape."
+    },
+    "notunicode4": {
+        "type": "string",
+        "value": "This string does not have a unicode \\\u0075 escape."
     }
 }
diff --git a/test/BurntSushi/valid/string-escapes.toml b/test/BurntSushi/valid/string-escapes.toml
--- a/test/BurntSushi/valid/string-escapes.toml
+++ b/test/BurntSushi/valid/string-escapes.toml
@@ -4,5 +4,8 @@
 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."
+notunicode1 = "This string does not have a unicode \\u escape."
+notunicode2 = "This string does not have a unicode \u005Cu escape."
+notunicode3 = "This string does not have a unicode \\u0075 escape."
+notunicode4 = "This string does not have a unicode \\\u0075 escape."
diff --git a/test/BurntSushi/valid/table-whitespace.toml b/test/BurntSushi/valid/table-whitespace.toml
--- a/test/BurntSushi/valid/table-whitespace.toml
+++ b/test/BurntSushi/valid/table-whitespace.toml
@@ -1,1 +1,1 @@
-[valid key]
+["valid key"]
diff --git a/test/BurntSushi/valid/table-with-pound.toml b/test/BurntSushi/valid/table-with-pound.toml
--- a/test/BurntSushi/valid/table-with-pound.toml
+++ b/test/BurntSushi/valid/table-with-pound.toml
@@ -1,2 +1,2 @@
-[key#group]
+["key#group"]
 answer = 42
diff --git a/test/BurntSushi/valid/unicode-escape.json b/test/BurntSushi/valid/unicode-escape.json
--- a/test/BurntSushi/valid/unicode-escape.json
+++ b/test/BurntSushi/valid/unicode-escape.json
@@ -1,3 +1,4 @@
 {
-    "answer": {"type": "string", "value": "\u03B4"}
+    "answer4": {"type": "string", "value": "\u03B4"},
+    "answer8": {"type": "string", "value": "\u03B4"}
 }
diff --git a/test/BurntSushi/valid/unicode-escape.toml b/test/BurntSushi/valid/unicode-escape.toml
--- a/test/BurntSushi/valid/unicode-escape.toml
+++ b/test/BurntSushi/valid/unicode-escape.toml
@@ -1,1 +1,2 @@
-answer = "\u03B4"
+answer4 = "\u03B4"
+answer8 = "\U000003B4"
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -1,6 +1,6 @@
 module Main where
 
-
+import           Prelude               hiding (readFile)  -- needed for GHCi, see `.ghci`
 import           Test.Tasty            (defaultMain, testGroup)
 
 import qualified BurntSushi
diff --git a/test/Text/Toml/Parser/Spec.hs b/test/Text/Toml/Parser/Spec.hs
--- a/test/Text/Toml/Parser/Spec.hs
+++ b/test/Text/Toml/Parser/Spec.hs
@@ -22,11 +22,11 @@
 
     it "should parse non-empty tomlDocs that do not end with a newline" $
       testParser tomlDoc "number = 123" $
-        fromList [("number", NTValue $ VInteger 123)]
+        fromList [("number", VInteger 123)]
 
     it "should parse when tomlDoc ends in a comment" $
       testParser tomlDoc "q = 42  # understood?" $
-        fromList [("q", NTValue $ VInteger 42)]
+        fromList [("q", VInteger 42)]
 
     it "should not parse re-assignment of key" $
       testParserFails tomlDoc "q=42\nq=42"
@@ -39,24 +39,28 @@
 
     it "should parse simple named table" $
       testParser tomlDoc "[a]\naa = 108" $
-        fromList [("a", NTable (fromList [("aa", NTValue $ VInteger 108)] ))]
+        fromList [("a", VTable (fromList [("aa", VInteger 108)] ))]
 
     it "should not parse redefined table header (key already exists at scope)" $
-      testParser tomlDoc "[a]\n[a]" $ fromList [("a", emptyNTable)]
+      testParserFails tomlDoc "[a]\n[a]"
 
     it "should parse redefinition of implicit key" $
       testParser tomlDoc "[a.b]\n[a]" $
-        fromList [("a", NTable (fromList [("b", emptyNTable)] ))]
+        fromList [("a", VTable (fromList [("b", VTable emptyTable)] ))]
 
     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)]))]
+        fromList [("a", VTable (fromList [("b", VTable (fromList [("b", VInteger 3)])),
+                                          ("a", VInteger 4)]))]
 
     it "should parse redefinition by implicit table header" $
       testParser tomlDoc "[a]\n[a.b]" $
-        fromList [("a", NTable (fromList [("b", emptyNTable)] ))]
+        fromList [("a", VTable (fromList [("b", VTable emptyTable)] ))]
 
+    it "should parse inline tables the same as normal tables" $
+      testParser tomlDoc "[a]\nb={}" $
+        fromList [("a", VTable (fromList [("b", VTable emptyTable)] ))]
+
     it "should not parse redefinition key" $
       testParserFails tomlDoc "[a]\nb=1\n[a.b]"
 
@@ -65,26 +69,26 @@
 
     it "should parse a simple empty table array" $
       testParser tomlDoc "[[a]]\n[[a]]" $
-        fromList [("a", NTArray [ fromList []
+        fromList [("a", VTArray [ 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)] ] )]
+        fromList [("a", VTArray [ fromList [("a1", VInteger 1)]
+                                , fromList [("a2", 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
+        fromList [("a", VTable (fromList [("b", VTArray [ 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)]
+        fromList [("a", VTable (fromList [("b", VTArray [ fromList [("a1", VInteger 1)]
+                                                        , fromList [("a2", VInteger 2)]
                                                         ] )] ) )]
 
     it "should parse redefined implicit table header" $
@@ -92,7 +96,7 @@
 
     it "should parse redefinition by implicit table header" $
       testParser tomlDoc "[[a]]\n[[a.b]]" $
-        fromList [("a", NTArray [ fromList [("b", NTArray [ fromList [] ])] ] )]
+        fromList [("a", VTArray [ fromList [("b", VTArray [ fromList [] ])] ] )]
 
 
   describe "Parser.tomlDoc (mixed named tables and tables arrays)" $ do
@@ -108,27 +112,27 @@
 
     it "should parse redefined implicit table header (array by table)" $
       testParser tomlDoc "[[a.b]]\n[a]" $
-        fromList [("a", NTable (fromList [("b", NTArray [ fromList [] ])] ) )]
+        fromList [("a", VTable (fromList [("b", VTArray [ 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) ]
+        fromList [("a", VTArray [ fromList [ ("i", VInteger 0) ]
+                                , fromList [ ("b", VTable  $ fromList [] )
+                                           , ("i", VInteger 1) ]
                                 ] )]
 
     it "should insert sub-key of table array" $
       testParser tomlDoc "[a]\n[[a.b]]" $
-        fromList [("a", NTable (fromList [("b", NTArray [fromList []])] ) )]
+        fromList [("a", VTable (fromList [("b", VTArray [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)]
+        fromList [("a", VTable (fromList [ ("q", VInteger 42),
+                                           ("b", VTArray [
+                                                   fromList [("i", VInteger 0)]
                                                    ]) ]) )]
 
   describe "Parser.headerValue" $ do
@@ -187,13 +191,7 @@
     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" $
@@ -209,10 +207,10 @@
   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\"/\\"
+      testParser basicStr "\"123\\b\\t\\n\\f\\r\\\"\\/\\\\\"" $ "123\b\t\n\f\r\"/\\"
 
     it "should parse the simple unicode value from the example" $
-      testParser basicStr "\"中国\"" $ VString "中国"
+      testParser basicStr "\"中国\"" $ "中国"
 
     it "should parse escaped 4 digit unicode values" $
       testParser assignment "special_k = \"\\u0416\"" ("special_k", VString "Ж")
@@ -227,16 +225,19 @@
   describe "Parser.multiBasicStr" $ do
 
     it "should parse simple example" $
-      testParser multiBasicStr "\"\"\"thorrough\"\"\"" $ VString "thorrough"
+      testParser multiBasicStr "\"\"\"thorrough\"\"\"" $  "thorrough"
 
+    it "should parse text containing a quote" $
+      testParser multiBasicStr "\"\"\"is \"it\" complete\"\"\"" $ "is \"it\" complete"
+
     it "should parse with newlines" $
-      testParser multiBasicStr "\"\"\"One\nTwo\"\"\"" $ VString "One\nTwo"
+      testParser multiBasicStr "\"\"\"One\nTwo\"\"\"" $  "One\nTwo"
 
     it "should parse with escaped newlines" $
-      testParser multiBasicStr "\"\"\"One\\\nTwo\"\"\"" $ VString "OneTwo"
+      testParser multiBasicStr "\"\"\"One\\\nTwo\"\"\"" $  "OneTwo"
 
     it "should parse newlines, ignoring 1 leading newline" $
-      testParser multiBasicStr "\"\"\"\nOne\\\nTwo\"\"\"" $ VString "OneTwo"
+      testParser multiBasicStr "\"\"\"\nOne\\\nTwo\"\"\"" $  "OneTwo"
 
     it "should parse with espaced whitespace" $
       testParser multiBasicStr "\"\"\"\\\n\
@@ -244,14 +245,17 @@
                                \\\\n\
                                \Jumped \\\n\
                                \Lazy\\\n\
-                               \ \"\"\"" $ VString "Quick Jumped Lazy"
+                               \ \"\"\"" $  "Quick Jumped Lazy"
 
+    it "should parse espaced on first" $
+      testParser multiBasicStr "\"\"\"\\\nQuick \\\n\\\nJumped \\\nLazy\\\n\"\"\"" $  "Quick Jumped Lazy"
 
+
   describe "Parser.literalStr" $ do
 
     it "should parse literally" $
       testParser literalStr "'\"Your\" folder: \\\\User\\new\\tmp\\'" $
-                            VString "\"Your\" folder: \\\\User\\new\\tmp\\"
+                             "\"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"
@@ -262,7 +266,7 @@
     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?"
+        $  "First newline is dropped.\n   Other whitespace,\n  is preserved -- isn't it?"
 
 
   describe "Parser.datetime" $ do
@@ -304,7 +308,18 @@
     it "should not accept floats without any decimals" $
       testParserFails float "5."
 
+    it "should accept floats which contain underscores" $
+      testParser float "5_3.4_5" $ VFloat 53.45
 
+    it "should not accept floats which end with underscores" $
+      testParserFails float "5_3.4_5_"
+
+    it "should not accept floats which start with underscores" $
+      testParserFails float "_5_3.4_5"
+
+    it "should not accept floats which have two consecutive underscores" $
+      testParserFails float "5__3.4__5"
+
   describe "Parser.integer" $ do
 
     it "should parse positive integers" $
@@ -319,7 +334,18 @@
     it "should parse integers prefixed with a plus" $
       testParser integer "+42" $ VInteger 42
 
+    it "should accept integers which contain underscores" $
+      testParser integer "4_2" $ VInteger 42
 
+    it "should not accept integers which end with underscores" $
+      testParserFails integer "4_2_"
+
+    it "should not accept integers which start with underscores" $
+      testParserFails integer "_4_2"
+
+    it "should not accept integers which have two consecutive underscores" $
+      testParserFails integer "4__2"
+
   describe "Parser.tomlDoc arrays" $ do
 
     it "should parse an empty array" $
@@ -384,6 +410,16 @@
     it "should parse terminating commas in arrays(2)" $
       testParser array "[1,2,]" $ VArray [ VInteger 1, VInteger 2 ]
 
+  describe "Parser.tomlDoc inline tables" $ do
+
+    it "should parse an empty inline table" $
+      testParser inlineTable "{}" $ VTable (fromList [])
+
+    it "should parse simple inline tables" $
+      testParser inlineTable "{ a = 8 , b = \"things\" }" $ VTable (fromList [ ("a" , VInteger 8) , ("b", VString "things") ])
+
+    it "should not parse simple inline tables with newline " $
+      testParserFails inlineTable "{ a = 8 , \n b = \"things\" }"
 
   where
     testParser p str success = case parseOnly p str of Left  _ -> False
