Repository: sdispater/tomlkit
Pull request #27 (2018-10-24 17:24:16)

Added AppVeyor badge
Because badges.

codecov-io: # [Codecov](https://codecov.io/gh/sdispater/tomlkit/pull/27?src=pr&el=h1) Report
> Merging [#27](https://codecov.io/gh/sdispater/tomlkit/pull/27?src=pr&el=desc) into [master](https://codecov.io/gh/sdispater/tomlkit/commit/353318d11895a3f977a46398fdc3e240a5275cfe?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/sdispater/tomlkit/pull/27/graphs/tree.svg?width=650&token=ZE9lS2yLXi&height=150&src=pr)](https://codecov.io/gh/sdispater/tomlkit/pull/27?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master      #27   +/-   ##
=======================================
  Coverage   90.86%   90.86%           
=======================================
  Files          10       10           
  Lines        1883     1883           
=======================================
  Hits         1711     1711           
  Misses        172      172
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/sdispater/tomlkit/pull/27?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sdispater/tomlkit/pull/27?src=pr&el=footer). Last update [353318d...9b25c91](https://codecov.io/gh/sdispater/tomlkit/pull/27?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b967139..e4b7c57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 # Change Log
 
+## [Unreleased]
+
+### Changed
+
+- Improved distinction between date(time)s and numbers.
+
+### Fixed
+
+- Fixed comma handling when parsing arrays. (Thanks to [@njalerikson](https://github.com/njalerikson))
+- Fixed comma handling when parsing inline tables. (Thanks to [@njalerikson](https://github.com/njalerikson))
+- Fixed a `KeyAlreadyPresent` error when declaring a sub table after other tables.
+
+
 ## [0.4.6] - 2018-10-16
 
 ### Fixed
diff --git a/README.md b/README.md
index 9cbadde..8fef631 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,20 @@
-[![GitHub Release](https://img.shields.io/github/release/sdispater/tomlkit.svg)](https://github.com/sdispater/tomlkit/releases/)
-[![PyPI Version](https://img.shields.io/pypi/v/tomlkit.svg)](https://pypi.python.org/pypi/tomlkit/)
-[![Python Versions](https://img.shields.io/pypi/pyversions/tomlkit.svg)](https://pypi.python.org/pypi/tomlkit/)
-[![License](https://img.shields.io/github/license/sdispater/tomlkit.svg)](https://github.com/sdispater/tomlkit/blob/master/LICENSE)
-[![Travis CI](https://img.shields.io/travis/com/sdispater/tomlkit.svg)](https://travis-ci.com/sdispater/tomlkit)
-[![Codecov](https://img.shields.io/codecov/c/github/sdispater/tomlkit/master.svg)](https://codecov.io/gh/sdispater/tomlkit)
-
+[github_release]: https://img.shields.io/github/release/sdispater/tomlkit.svg?logo=github&logoColor=white
+[pypi_version]: https://img.shields.io/pypi/v/tomlkit.svg?logo=python&logoColor=white
+[python_versions]: https://img.shields.io/pypi/pyversions/tomlkit.svg?logo=python&logoColor=white
+[github_license]: https://img.shields.io/github/license/sdispater/tomlkit.svg?logo=github&logoColor=white
+[travisci]: https://img.shields.io/travis/com/sdispater/tomlkit/master.svg?logo=travis&logoColor=white&label=Travis%20CI
+[appveyor]: https://img.shields.io/appveyor/ci/sdispater/tomlkit/master.svg?logo=appveyor&logoColor=white&label=AppVeyor
+<!--Codecov logo not offered by shields.io or simpleicons.org, this is Codecov's SVG image modified to be white-->
+[codecov]: https://img.shields.io/codecov/c/github/sdispater/tomlkit/master.svg?logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CgogPGc+CiAgPHRpdGxlPmJhY2tncm91bmQ8L3RpdGxlPgogIDxyZWN0IGZpbGw9Im5vbmUiIGlkPSJjYW52YXNfYmFja2dyb3VuZCIgaGVpZ2h0PSI0MDIiIHdpZHRoPSI1ODIiIHk9Ii0xIiB4PSItMSIvPgogPC9nPgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIGlkPSJzdmdfMSIgZmlsbC1ydWxlPSJldmVub2RkIiBmaWxsPSIjZmZmZmZmIiBkPSJtMjUuMDE0LDBjLTEzLjc4NCwwLjAxIC0yNS4wMDQsMTEuMTQ5IC0yNS4wMTQsMjQuODMybDAsMC4wNjJsNC4yNTQsMi40ODJsMC4wNTgsLTAuMDM5YTEyLjIzOCwxMi4yMzggMCAwIDEgOS4wNzgsLTEuOTI4YTExLjg0NCwxMS44NDQgMCAwIDEgNS45OCwyLjk3NWwwLjczLDAuNjhsMC40MTMsLTAuOTA0YzAuNCwtMC44NzQgMC44NjIsLTEuNjk2IDEuMzc0LC0yLjQ0M2MwLjIwNiwtMC4zIDAuNDMzLC0wLjYwNCAwLjY5MiwtMC45MjlsMC40MjcsLTAuNTM1bC0wLjUyNiwtMC40NGExNy40NSwxNy40NSAwIDAgMCAtOC4xLC0zLjc4MWExNy44NTMsMTcuODUzIDAgMCAwIC04LjM3NSwwLjQ5YzIuMDIzLC04Ljg2OCA5LjgyLC0xNS4wNSAxOS4wMjcsLTE1LjA1N2M1LjE5NSwwIDEwLjA3OCwyLjAwNyAxMy43NTIsNS42NTJjMi42MTksMi41OTggNC40MjIsNS44MzUgNS4yMjQsOS4zNzJhMTcuOTA4LDE3LjkwOCAwIDAgMCAtNS4yMDgsLTAuNzlsLTAuMzE4LC0wLjAwMWExOC4wOTYsMTguMDk2IDAgMCAwIC0yLjA2NywwLjE1M2wtMC4wODcsMC4wMTJjLTAuMzAzLDAuMDQgLTAuNTcsMC4wODEgLTAuODEzLDAuMTI2Yy0wLjExOSwwLjAyIC0wLjIzNywwLjA0NSAtMC4zNTUsMC4wNjhjLTAuMjgsMC4wNTcgLTAuNTU0LDAuMTE5IC0wLjgxNiwwLjE4NWwtMC4yODgsMC4wNzNjLTAuMzM2LDAuMDkgLTAuNjc1LDAuMTkxIC0xLjAwNiwwLjNsLTAuMDYxLDAuMDJjLTAuNzQsMC4yNTEgLTEuNDc4LDAuNTU4IC0yLjE5LDAuOTE0bC0wLjA1NywwLjAyOWMtMC4zMTYsMC4xNTggLTAuNjM2LDAuMzMzIC0wLjk3OCwwLjUzNGwtMC4wNzUsMC4wNDVhMTYuOTcsMTYuOTcgMCAwIDAgLTQuNDE0LDMuNzhsLTAuMTU3LDAuMTkxYy0wLjMxNywwLjM5NCAtMC41NjcsMC43MjcgLTAuNzg3LDEuMDQ4Yy0wLjE4NCwwLjI3IC0wLjM2OSwwLjU2IC0wLjYsMC45NDJsLTAuMTI2LDAuMjE3Yy0wLjE4NCwwLjMxOCAtMC4zNDgsMC42MjIgLTAuNDg3LDAuOWwtMC4wMzMsMC4wNjFjLTAuMzU0LDAuNzExIC0wLjY2MSwxLjQ1NSAtMC45MTcsMi4yMTRsLTAuMDM2LDAuMTExYTE3LjEzLDE3LjEzIDAgMCAwIC0wLjg1NSw1LjY0NGwwLjAwMywwLjIzNGEyMy41NjUsMjMuNTY1IDAgMCAwIDAuMDQzLDAuODIyYzAuMDEsMC4xMyAwLjAyMywwLjI1OSAwLjAzNiwwLjM4OGMwLjAxNSwwLjE1OCAwLjAzNCwwLjMxNiAwLjA1MywwLjQ3MWwwLjAxMSwwLjA4OGwwLjAyOCwwLjIxNGMwLjAzNywwLjI2NCAwLjA4LDAuNTI1IDAuMTMsMC43ODdjMC41MDMsMi42MzcgMS43Niw1LjI3NCAzLjYzNSw3LjYyNWwwLjA4NSwwLjEwNmwwLjA4NywtMC4xMDRjMC43NDgsLTAuODg0IDIuNjAzLC0zLjY4NyAyLjc2LC01LjM2OWwwLjAwMywtMC4wMzFsLTAuMDE1LC0wLjAyOGExMS43MzYsMTEuNzM2IDAgMCAxIC0xLjMzMywtNS40MDdjMCwtNi4yODQgNC45NCwtMTEuNTAyIDExLjI0MywtMTEuODhsMC40MTQsLTAuMDE1YzIuNTYxLC0wLjA1OCA1LjA2NCwwLjY3MyA3LjIzLDIuMTM2bDAuMDU4LDAuMDM5bDQuMTk3LC0yLjQ0bDAuMDU1LC0wLjAzM2wwLC0wLjA2MmMwLjAwNiwtNi42MzIgLTIuNTkyLC0xMi44NjUgLTcuMzE0LC0xNy41NTFjLTQuNzE2LC00LjY3OSAtMTAuOTkxLC03LjI1NSAtMTcuNjcyLC03LjI1NSIvPgogPC9nPgo8L3N2Zz4=&label=Codecov
+
+[![GitHub Release][github_release]](https://github.com/sdispater/tomlkit/releases/)
+[![PyPI Version][pypi_version]](https://pypi.python.org/pypi/tomlkit/)
+[![Python Versions][python_versions]](https://pypi.python.org/pypi/tomlkit/)
+[![License][github_license]](https://github.com/sdispater/tomlkit/blob/master/LICENSE)
+<br>
+[![Travis CI][travisci]](https://travis-ci.com/sdispater/tomlkit)
+[![AppVeyor][appveyor]](https://ci.appveyor.com/project/sdispater/tomlkit)
+[![Codecov][codecov]](https://codecov.io/gh/sdispater/tomlkit)
 
 # TOML Kit - Style-preserving TOML library for Python
 
diff --git a/tomlkit/_utils.py b/tomlkit/_utils.py
index f62a354..0a68be9 100644
--- a/tomlkit/_utils.py
+++ b/tomlkit/_utils.py
@@ -9,19 +9,30 @@ from datetime import timedelta
 from ._compat import decode
 from ._compat import timezone
 
+RFC_3339_LOOSE = re.compile(
+    "^"
+    r"(([0-9]+)-(\d{2})-(\d{2}))?"  # Date
+    "("
+    "([T ])?"  # Separator
+    r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?"  # Time
+    r"((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?"  # Timezone
+    ")?"
+    "$"
+)
+
 RFC_3339_DATETIME = re.compile(
     "^"
     "([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"  # Date
     "[T ]"  # Separator
-    "([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?"  # Time
-    "((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?"  # Timezone
+    r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?"  # Time
+    r"((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?"  # Timezone
     "$"
 )
 
 RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$")
 
 RFC_3339_TIME = re.compile(
-    "^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$"
+    r"^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$"
 )
 
 _utc = timezone(timedelta(), "UTC")
diff --git a/tomlkit/container.py b/tomlkit/container.py
index f1d7e17..24613b0 100644
--- a/tomlkit/container.py
+++ b/tomlkit/container.py
@@ -81,7 +81,7 @@ class Container(dict):
 
         return self.append(key, item)
 
-    def append(self, key, item):  # type: (Union[Key, str], Item) -> Container
+    def append(self, key, item):  # type: (Union[Key, str, None], Item) -> Container
         if not isinstance(key, Key) and key is not None:
             key = Key(key)
 
@@ -106,7 +106,11 @@ class Container(dict):
                 self.append(None, Whitespace("\n"))
 
         if key is not None and key in self:
-            current = self._body[self._map[key]][1]
+            current_idx = self._map[key]
+            if isinstance(current_idx, tuple):
+                current_idx = current_idx[0]
+
+            current = self._body[current_idx][1]
             if isinstance(item, Table):
                 if not isinstance(current, (Table, AoT)):
                     raise KeyAlreadyPresent(key)
@@ -128,7 +132,7 @@ class Container(dict):
                             current.append(k, v)
 
                         return self
-                else:
+                elif not item.is_super_table():
                     raise KeyAlreadyPresent(key)
             elif isinstance(item, AoT):
                 if not isinstance(current, AoT):
@@ -180,7 +184,23 @@ class Container(dict):
             else:
                 return self._insert_at(0, key, item)
 
-        self._map[key] = len(self._body)
+        if key in self._map:
+            current_idx = self._map[key]
+            if isinstance(current_idx, tuple):
+                current_idx = current_idx[0]
+
+            current = self._body[current_idx][1]
+            if key is not None and not isinstance(current, Table):
+                raise KeyAlreadyPresent(key)
+
+            # Adding sub tables to a currently existing table
+            idx = self._map[key]
+            if not isinstance(idx, tuple):
+                idx = (idx,)
+
+            self._map[key] = idx + (len(self._body),)
+        else:
+            self._map[key] = len(self._body)
 
         self._body.append((key, item))
 
@@ -227,7 +247,16 @@ class Container(dict):
 
         # Increment indices after the current index
         for k, v in self._map.items():
-            if v > idx:
+            if isinstance(v, tuple):
+                new_indices = []
+                for v_ in v:
+                    if v_ > idx:
+                        v_ = v_ + 1
+
+                    new_indices.append(v_)
+
+                self._map[k] = tuple(new_indices)
+            elif v > idx:
                 self._map[k] = v + 1
 
         self._map[other_key] = idx + 1
@@ -260,7 +289,16 @@ class Container(dict):
 
         # Increment indices after the current index
         for k, v in self._map.items():
-            if v >= idx:
+            if isinstance(v, tuple):
+                new_indices = []
+                for v_ in v:
+                    if v_ >= idx:
+                        v_ = v_ + 1
+
+                    new_indices.append(v_)
+
+                self._map[k] = tuple(new_indices)
+            elif v >= idx:
                 self._map[k] = v + 1
 
         self._map[key] = idx
@@ -289,29 +327,7 @@ class Container(dict):
         s = ""
         for k, v in self._body:
             if k is not None:
-                if False:
-                    key = k.as_string()
-
-                    for _k, _v in v.value.body:
-                        if _k is None:
-                            s += v.as_string()
-                        elif isinstance(_v, Table):
-                            s += v.as_string(prefix=key)
-                        else:
-                            _key = key
-                            if prefix is not None:
-                                _key = prefix + "." + _key
-
-                            s += "{}{}{}{}{}{}{}".format(
-                                _v.trivia.indent,
-                                _key + "." + decode(_k.as_string()),
-                                _k.sep,
-                                decode(_v.as_string()),
-                                _v.trivia.comment_ws,
-                                decode(_v.trivia.comment),
-                                _v.trivia.trail,
-                            )
-                elif isinstance(v, Table):
+                if isinstance(v, Table):
                     s += self._render_table(k, v)
                 elif isinstance(v, AoT):
                     s += self._render_aot(k, v)
@@ -476,6 +492,20 @@ class Container(dict):
         if idx is None:
             raise NonExistentKey(key)
 
+        if isinstance(idx, tuple):
+            container = Container(True)
+
+            for i in idx:
+                item = self._body[i][1]
+
+                if isinstance(item, Table):
+                    for k, v in item.value.body:
+                        container.append(k, v)
+                else:
+                    container.append(key, item)
+
+            return container
+
         item = self._body[idx][1]
 
         return item.value
diff --git a/tomlkit/exceptions.py b/tomlkit/exceptions.py
index 7f88f95..17a1ba4 100644
--- a/tomlkit/exceptions.py
+++ b/tomlkit/exceptions.py
@@ -26,6 +26,14 @@ class ParseError(ValueError, TOMLKitError):
             "{} at line {} col {}".format(message, self._line, self._col)
         )
 
+    @property
+    def line(self):
+        return self._line
+
+    @property
+    def col(self):
+        return self._col
+
 
 class MixedArrayTypesError(ParseError):
     """
@@ -38,6 +46,50 @@ class MixedArrayTypesError(ParseError):
         super(MixedArrayTypesError, self).__init__(line, col, message=message)
 
 
+class InvalidNumberError(ParseError):
+    """
+    A numeric field was improperly specified.
+    """
+
+    def __init__(self, line, col):  # type: (int, int) -> None
+        message = "Invalid number"
+
+        super(InvalidNumberError, self).__init__(line, col, message=message)
+
+
+class InvalidDateTimeError(ParseError):
+    """
+    A datetime field was improperly specified.
+    """
+
+    def __init__(self, line, col):  # type: (int, int) -> None
+        message = "Invalid datetime"
+
+        super(InvalidDateTimeError, self).__init__(line, col, message=message)
+
+
+class InvalidDateError(ParseError):
+    """
+    A date field was improperly specified.
+    """
+
+    def __init__(self, line, col):  # type: (int, int) -> None
+        message = "Invalid date"
+
+        super(InvalidDateError, self).__init__(line, col, message=message)
+
+
+class InvalidTimeError(ParseError):
+    """
+    A date field was improperly specified.
+    """
+
+    def __init__(self, line, col):  # type: (int, int) -> None
+        message = "Invalid time"
+
+        super(InvalidTimeError, self).__init__(line, col, message=message)
+
+
 class InvalidNumberOrDateError(ParseError):
     """
     A numeric or date field was improperly specified.
@@ -109,7 +161,9 @@ class InternalParserError(ParseError):
     An error that indicates a bug in the parser.
     """
 
-    def __init__(self, line, col, message=None):  # type: (int, int) -> None
+    def __init__(
+        self, line, col, message=None
+    ):  # type: (int, int, Optional[str]) -> None
         msg = "Internal parser error"
         if message:
             msg += " ({})".format(message)
@@ -137,15 +191,3 @@ class KeyAlreadyPresent(TOMLKitError):
         message = 'Key "{}" already exists.'.format(key)
 
         super(KeyAlreadyPresent, self).__init__(message)
-
-
-class Restore(TOMLKitError):
-    """
-    The current parsing path failed, restore to the previous state. This should only
-    be raised within a with the state handler context manager.
-    """
-
-    def __init__(self):  # type: () -> None
-        message = "Restore to the previous state."
-
-        super(Restore, self).__init__(message)
diff --git a/tomlkit/items.py b/tomlkit/items.py
index 059bbc1..62bb9ed 100644
--- a/tomlkit/items.py
+++ b/tomlkit/items.py
@@ -124,6 +124,24 @@ class StringType(Enum):
         }[self]
 
 
+class BoolType(Enum):
+    TRUE = "true"
+    FALSE = "false"
+
+    @lru_cache(maxsize=None)
+    def __bool__(self):
+        return {BoolType.TRUE: True, BoolType.FALSE: False}[self]
+
+    if PY2:
+        __nonzero__ = __bool__  # for PY2
+
+    def __iter__(self):
+        return iter(self.value)
+
+    def __len__(self):
+        return len(self.value)
+
+
 class Trivia:
     """
     Trivia information (aka metadata).
@@ -449,10 +467,10 @@ class Bool(Item):
     A boolean literal.
     """
 
-    def __init__(self, value, trivia):  # type: (float, Trivia) -> None
+    def __init__(self, t, trivia):  # type: (float, Trivia) -> None
         super(Bool, self).__init__(trivia)
 
-        self._value = value
+        self._value = bool(t)
 
     @property
     def discriminant(self):  # type: () -> int
diff --git a/tomlkit/parser.py b/tomlkit/parser.py
index 7d479d9..242358e 100644
--- a/tomlkit/parser.py
+++ b/tomlkit/parser.py
@@ -1,11 +1,12 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 
-import datetime
 import re
 import string
 
-from typing import Iterator
+from typing import Any
+from typing import Generator
+from typing import List
 from typing import Optional
 from typing import Tuple
 from typing import Union
@@ -13,27 +14,32 @@ from typing import Union
 from ._compat import chr
 from ._compat import decode
 from ._utils import _escaped
+from ._utils import RFC_3339_LOOSE
 from ._utils import parse_rfc3339
 from .container import Container
 from .exceptions import EmptyKeyError
 from .exceptions import EmptyTableNameError
 from .exceptions import InternalParserError
 from .exceptions import InvalidCharInStringError
-from .exceptions import InvalidNumberOrDateError
+from .exceptions import InvalidDateTimeError
+from .exceptions import InvalidDateError
+from .exceptions import InvalidTimeError
+from .exceptions import InvalidNumberError
 from .exceptions import MixedArrayTypesError
 from .exceptions import ParseError
 from .exceptions import UnexpectedCharError
 from .exceptions import UnexpectedEofError
-from .exceptions import Restore
 from .items import AoT
 from .items import Array
 from .items import Bool
+from .items import BoolType
 from .items import Comment
 from .items import Date
 from .items import DateTime
 from .items import Float
 from .items import InlineTable
 from .items import Integer
+from .items import Item
 from .items import Key
 from .items import KeyType
 from .items import Null
@@ -81,20 +87,26 @@ class Parser:
         """
         return self._src.extract()
 
-    def inc(self, exception=None):  # type: () -> bool
+    def inc(self, exception=None):  # type: (Optional[ParseError.__class__]) -> bool
         """
         Increments the parser if the end of the input has not been reached.
         Returns whether or not it was able to advance.
         """
         return self._src.inc(exception=exception)
 
-    def inc_n(self, n, exception=None):  # type: (int) -> bool
+    def inc_n(self, n, exception=None):  # type: (int, Optional[ParseError]) -> bool
         """
         Increments the parser by n characters
         if the end of the input has not been reached.
         """
         return self._src.inc_n(n=n, exception=exception)
 
+    def consume(self, chars, min=0, max=-1):
+        """
+        Consume chars until min/max is satisfied is valid.
+        """
+        return self._src.consume(chars=chars, min=min, max=max)
+
     def end(self):  # type: () -> bool
         """
         Returns True if the parser has reached the end of the input.
@@ -240,17 +252,17 @@ class Parser:
                     # Found a newline; Return all whitespace found up to this point.
                     self.inc()
 
-                    return (None, Whitespace(self.extract()))
+                    return None, Whitespace(self.extract())
                 elif c in " \t\r":
                     # Skip whitespace.
                     if not self.inc():
-                        return (None, Whitespace(self.extract()))
+                        return None, Whitespace(self.extract())
                 elif c == "#":
                     # Found a comment, parse it
                     indent = self.extract()
                     cws, comment, trail = self._parse_comment_trail()
 
-                    return (None, Comment(Trivia(indent, cws, comment, trail)))
+                    return None, Comment(Trivia(indent, cws, comment, trail))
                 elif c == "[":
                     # Found a table, delegate to the calling function.
                     return
@@ -318,9 +330,7 @@ class Parser:
 
         return comment_ws, comment, trail
 
-    def _parse_key_value(
-        self, parse_comment=False, inline=True
-    ):  # type: (bool, bool) -> (Key, Item)
+    def _parse_key_value(self, parse_comment=False):  # type: (bool) -> (Key, Item)
         # Leading indent
         self.mark()
 
@@ -430,7 +440,7 @@ class Parser:
 
     def _handle_dotted_key(
         self, container, key, value
-    ):  # type: (Container, Key) -> None
+    ):  # type: (Container, Key, Any) -> None
         names = tuple(self._split_table_name(key.key))
         name = names[0]
         name._dotted = True
@@ -467,121 +477,199 @@ class Parser:
         Attempts to parse a value at the current position.
         """
         self.mark()
+        c = self._current
+        trivia = Trivia()
 
-        with self._state:
+        if c == StringType.SLB.value:
             return self._parse_basic_string()
-
-        with self._state:
+        elif c == StringType.SLL.value:
             return self._parse_literal_string()
+        elif c == BoolType.TRUE.value[0]:
+            return self._parse_true()
+        elif c == BoolType.FALSE.value[0]:
+            return self._parse_false()
+        elif c == "[":
+            return self._parse_array()
+        elif c == "{":
+            return self._parse_inline_table()
+        elif c in "+-" or self._peek(4) in {
+            "+inf",
+            "-inf",
+            "inf",
+            "+nan",
+            "-nan",
+            "nan",
+        }:
+            # Number
+            while self._current not in " \t\n\r#,]}" and self.inc():
+                pass
 
-        trivia = Trivia()
-        c = self._current
-        if c == "t" and self._src[self._idx :].startswith("true"):
-            # Boolean: true
-            self.inc_n(4)
+            raw = self.extract()
 
-            return Bool(True, trivia)
-        elif c == "f" and self._src[self._idx :].startswith("false"):
-            # Boolean: true
-            self.inc_n(5)
+            item = self._parse_number(raw, trivia)
+            if item is not None:
+                return item
 
-            return Bool(False, trivia)
-        elif c == "[":
-            # Array
-            elems = []  # type: List[Item]
-            self.inc()
+            raise self.parse_error(InvalidNumberError)
+        elif c in string.digits:
+            # Integer, Float, Date, Time or DateTime
+            while self._current not in " \t\n\r#,]}" and self.inc():
+                pass
 
-            while self._current != "]":
-                self.mark()
-                while self._current.is_ws() or self._current == ",":
-                    self.inc()
+            raw = self.extract()
 
-                if self._idx != self._marker:
-                    elems.append(Whitespace(self.extract()))
+            m = RFC_3339_LOOSE.match(raw)
+            if m:
+                if m.group(1) and m.group(5):
+                    # datetime
+                    try:
+                        return DateTime(parse_rfc3339(raw), trivia, raw)
+                    except ValueError:
+                        raise self.parse_error(InvalidDateTimeError)
+
+                if m.group(1):
+                    try:
+                        return Date(parse_rfc3339(raw), trivia, raw)
+                    except ValueError:
+                        raise self.parse_error(InvalidDateError)
+
+                if m.group(5):
+                    try:
+                        return Time(parse_rfc3339(raw), trivia, raw)
+                    except ValueError:
+                        raise self.parse_error(InvalidTimeError)
 
-                if self._current == "]":
-                    break
+            item = self._parse_number(raw, trivia)
+            if item is not None:
+                return item
 
-                if self._current == "#":
-                    cws, comment, trail = self._parse_comment_trail()
+            raise self.parse_error(InvalidNumberError)
+        else:
+            raise self.parse_error(UnexpectedCharError, c)
 
-                    next_ = Comment(Trivia("", cws, comment, trail))
-                else:
-                    next_ = self._parse_value()
+    def _parse_true(self):
+        return self._parse_bool(BoolType.TRUE)
 
-                elems.append(next_)
+    def _parse_false(self):
+        return self._parse_bool(BoolType.FALSE)
 
-            self.inc()
+    def _parse_bool(self, style):  # type: (BoolType) -> Bool
+        with self._state:
+            style = BoolType(style)
 
-            try:
-                res = Array(elems, trivia)
-            except ValueError:
-                raise self.parse_error(MixedArrayTypesError)
+            # only keep parsing for bool if the characters match the style
+            # try consuming rest of chars in style
+            for c in style:
+                self.consume(c, min=1, max=1)
 
-            if res.is_homogeneous():
-                return res
+            return Bool(style, Trivia())
 
-            raise self.parse_error(MixedArrayTypesError)
-        elif c == "{":
-            # Inline table
-            elems = Container(True)
-            self.inc()
+    def _parse_array(self):  # type: () -> Array
+        # Consume opening bracket, EOF here is an issue (middle of array)
+        self.inc(exception=UnexpectedEofError)
 
-            while self._current != "}":
-                self.mark()
-                while self._current.is_spaces() or self._current == ",":
-                    self.inc()
+        elems = []  # type: List[Item]
+        prev_value = None
+        while True:
+            # consume whitespace
+            mark = self._idx
+            self.consume(TOMLChar.SPACES)
+            newline = self.consume(TOMLChar.NL)
+            indent = self._src[mark : self._idx]
+            if newline:
+                elems.append(Whitespace(indent))
+                continue
 
-                if self._idx != self._marker:
-                    ws = self.extract().lstrip(",")
-                    if ws:
-                        elems.append(None, Whitespace(ws))
+            # consume comment
+            if self._current == "#":
+                cws, comment, trail = self._parse_comment_trail()
+                elems.append(Comment(Trivia(indent, cws, comment, trail)))
+                continue
 
-                if self._current == "}":
-                    break
+            # consume indent
+            if indent:
+                elems.append(Whitespace(indent))
+                continue
 
-                key, val = self._parse_key_value(False, inline=True)
-                elems.append(key, val)
+            # consume value
+            if not prev_value:
+                try:
+                    elems.append(self._parse_value())
+                    prev_value = True
+                    continue
+                except UnexpectedCharError:
+                    pass
 
-            self.inc()
+            # consume comma
+            if prev_value and self._current == ",":
+                self.inc(exception=UnexpectedEofError)
+                elems.append(Whitespace(","))
+                prev_value = False
+                continue
 
-            return InlineTable(elems, trivia)
-        elif c in string.digits + "+-" or self._peek(4) in {
-            "+inf",
-            "-inf",
-            "inf",
-            "+nan",
-            "-nan",
-            "nan",
-        }:
-            # Integer, Float, Date, Time or DateTime
-            while self._current not in " \t\n\r#,]}" and self.inc():
-                pass
+            # consume closing bracket
+            if self._current == "]":
+                # consume closing bracket, EOF here doesn't matter
+                self.inc()
+                break
 
-            raw = self.extract()
+            raise self.parse_error(UnexpectedCharError, self._current)
 
-            item = self._parse_number(raw, trivia)
-            if item is not None:
-                return item
+        try:
+            res = Array(elems, Trivia())
+        except ValueError:
+            pass
+        else:
+            if res.is_homogeneous():
+                return res
 
-            try:
-                res = parse_rfc3339(raw)
-            except ValueError:
-                res = None
+        raise self.parse_error(MixedArrayTypesError)
 
-            if res is None:
-                raise self.parse_error(InvalidNumberOrDateError)
+    def _parse_inline_table(self):  # type: () -> InlineTable
+        # consume opening bracket, EOF here is an issue (middle of array)
+        self.inc(exception=UnexpectedEofError)
 
-            if isinstance(res, datetime.datetime):
-                return DateTime(res, trivia, raw)
-            elif isinstance(res, datetime.time):
-                return Time(res, trivia, raw)
-            elif isinstance(res, datetime.date):
-                return Date(res, trivia, raw)
+        elems = Container(True)
+        trailing_comma = None
+        while True:
+            # consume leading whitespace
+            mark = self._idx
+            self.consume(TOMLChar.SPACES)
+            raw = self._src[mark : self._idx]
+            if raw:
+                elems.add(Whitespace(raw))
+
+            if not trailing_comma:
+                # None: empty inline table
+                # False: previous key-value pair was not followed by a comma
+                if self._current == "}":
+                    # consume closing bracket, EOF here doesn't matter
+                    self.inc()
+                    break
+                if trailing_comma is False:
+                    raise self.parse_error(UnexpectedCharError, self._current)
             else:
-                raise self.parse_error(InvalidNumberOrDateError)
-        else:
-            raise self.parse_error(UnexpectedCharError, c)
+                # True: previous key-value pair was followed by a comma
+                if self._current == "}":
+                    raise self.parse_error(UnexpectedCharError, self._current)
+
+            key, val = self._parse_key_value(False)
+            elems.add(key, val)
+
+            # consume trailing whitespace
+            mark = self._idx
+            self.consume(TOMLChar.SPACES)
+            raw = self._src[mark : self._idx]
+            if raw:
+                elems.add(Whitespace(raw))
+
+            # consume trailing comma
+            trailing_comma = self._current == ","
+            if trailing_comma:
+                # consume closing bracket, EOF here is an issue (middle of inline table)
+                self.inc(exception=UnexpectedEofError)
+
+        return InlineTable(elems, Trivia())
 
     def _parse_number(self, raw, trivia):  # type: (str, Trivia) -> Optional[Item]
         # Leading zeros are not allowed
@@ -629,11 +717,13 @@ class Parser:
             except ValueError:
                 return
 
-    def _parse_literal_string(self):  # type: () -> Item
-        return self._parse_string(StringType.SLL)
+    def _parse_literal_string(self):  # type: () -> String
+        with self._state:
+            return self._parse_string(StringType.SLL)
 
-    def _parse_basic_string(self):  # type: () -> Item
-        return self._parse_string(StringType.SLB)
+    def _parse_basic_string(self):  # type: () -> String
+        with self._state:
+            return self._parse_string(StringType.SLB)
 
     def _parse_escaped_char(self, multiline):
         if multiline and self._current.is_ws():
@@ -678,13 +768,13 @@ class Parser:
 
         raise self.parse_error(InvalidCharInStringError, self._current)
 
-    def _parse_string(self, delim):  # type: (str) -> Item
-        delim = StringType(delim)
-        assert delim.is_singleline()
-
+    def _parse_string(self, delim):  # type: (StringType) -> String
         # only keep parsing for string if the current character matches the delim
         if self._current != delim.unit:
-            raise Restore
+            raise self.parse_error(
+                InternalParserError,
+                "Invalid character for string type {}".format(delim),
+            )
 
         # consume the opening/first delim, EOF here is an issue
         # (middle of string or middle of delim)
@@ -740,8 +830,6 @@ class Parser:
                     if not close:  # if there is no close characters, keep parsing
                         continue
                 else:
-                    close = delim.unit
-
                     # consume the closing delim, we do not care if EOF occurs as
                     # that would simply imply the end of self._src
                     self.inc()
@@ -987,7 +1075,7 @@ class Parser:
                 break
             return buf
 
-    def _peek_unicode(self, is_long):  # type: () -> Tuple[bool, str]
+    def _peek_unicode(self, is_long):  # type: (bool) -> Tuple[bool, str]
         """
         Peeks ahead non-intrusively by cloning then restoring the
         initial state of the parser.
diff --git a/tomlkit/source.py b/tomlkit/source.py
index 73b941d..1a96e05 100644
--- a/tomlkit/source.py
+++ b/tomlkit/source.py
@@ -4,12 +4,13 @@ from __future__ import unicode_literals
 import itertools
 
 from copy import copy
+from typing import Optional
+from typing import Tuple
 
 from ._compat import PY2
 from ._compat import unicode
 from .exceptions import UnexpectedEofError
 from .exceptions import UnexpectedCharError
-from .exceptions import Restore
 from .exceptions import ParseError
 from .toml_char import TOMLChar
 
@@ -48,7 +49,7 @@ class _State:
 
             # Restore exceptions are silently consumed, other exceptions need to
             # propagate
-            return exception_type == Restore
+            return exception_type is None
 
 
 class _StateHandler:
@@ -76,15 +77,21 @@ class _StateHandler:
 class Source(unicode):
     EOF = TOMLChar("\0")
 
-    def __init__(self, src):  # type: (unicode) -> None
+    def __init__(self, _):  # type: (unicode) -> None
         super(Source, self).__init__()
 
-        self.reset()
-
-    def reset(self):
         # Collection of TOMLChars
         self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self)])
 
+        self._idx = 0
+        self._marker = 0
+        self._current = TOMLChar("")
+
+        self._state = _StateHandler(self)
+
+        self.inc()
+
+    def reset(self):
         # initialize both idx and current
         self.inc()
 
@@ -93,11 +100,7 @@ class Source(unicode):
 
     @property
     def state(self):  # type: () -> _StateHandler
-        try:
-            return self._state
-        except AttributeError:
-            self._state = _StateHandler(self)
-            return self._state
+        return self._state
 
     @property
     def idx(self):  # type: () -> int
@@ -111,26 +114,27 @@ class Source(unicode):
     def marker(self):  # type: () -> int
         return self._marker
 
-    def extract(self):  # type: () -> None
+    def extract(self):  # type: () -> unicode
         """
         Extracts the value between marker and index
         """
         return self[self._marker : self._idx]
 
-    def inc(self, exception=None):  # type: (Exception) -> bool
+    def inc(self, exception=None):  # type: (Optional[ParseError.__class__]) -> bool
         """
         Increments the parser if the end of the input has not been reached.
         Returns whether or not it was able to advance.
         """
         try:
             self._idx, self._current = next(self._chars)
+
             return True
         except StopIteration:
             self._idx = len(self)
             self._current = self.EOF
             if exception:
-                exception = UnexpectedEofError if exception is True else exception
                 raise self.parse_error(exception)
+
             return False
 
     def inc_n(self, n, exception=None):  # type: (int, Exception) -> bool
@@ -144,7 +148,7 @@ class Source(unicode):
 
         return True
 
-    def consume(self, chars, min=0, max=-1, restore=True):
+    def consume(self, chars, min=0, max=-1):
         """
         Consume chars until min/max is satisfied is valid.
         """
@@ -156,7 +160,7 @@ class Source(unicode):
 
         # failed to consume minimum number of characters
         if min > 0:
-            raise Restore if restore else self.parse_error(UnexpectedCharError)
+            self.parse_error(UnexpectedCharError)
 
     def end(self):  # type: () -> bool
         """
@@ -170,14 +174,17 @@ class Source(unicode):
         """
         self._marker = self._idx
 
-    def parse_error(self, exception=ParseError, *args):  # type: () -> None
+    def parse_error(
+        self, exception=ParseError, *args
+    ):  # type: (ParseError.__class__, ...) -> ParseError
         """
         Creates a generic "parse error" at the current position.
         """
         line, col = self._to_linecol()
+
         return exception(line, col, *args)
 
-    def _to_linecol(self):  # type: (int) -> Tuple[int, int]
+    def _to_linecol(self):  # type: () -> Tuple[int, int]
         cur = 0
         for i, line in enumerate(self.splitlines()):
             if cur + len(line) + 1 > self.idx:
diff --git a/tomlkit/toml_char.py b/tomlkit/toml_char.py
index 92f1a9c..d649a91 100644
--- a/tomlkit/toml_char.py
+++ b/tomlkit/toml_char.py
@@ -16,44 +16,51 @@ class TOMLChar(unicode):
         if len(self) > 1:
             raise ValueError("A TOML character must be of length 1")
 
+    BARE = string.ascii_letters + string.digits + "-_"
+    KV = "= \t"
+    NUMBER = string.digits + "+-_.e"
+    SPACES = " \t"
+    NL = "\n\r"
+    WS = SPACES + NL
+
     @lru_cache(maxsize=None)
     def is_bare_key_char(self):  # type: () -> bool
         """
         Whether the character is a valid bare key name or not.
         """
-        return self in string.ascii_letters + string.digits + "-" + "_"
+        return self in self.BARE
 
     @lru_cache(maxsize=None)
     def is_kv_sep(self):  # type: () -> bool
         """
         Whether the character is a valid key/value separator ot not.
         """
-        return self in "= \t"
+        return self in self.KV
 
     @lru_cache(maxsize=None)
     def is_int_float_char(self):  # type: () -> bool
         """
         Whether the character if a valid integer or float value character or not.
         """
-        return self in string.digits + "+" + "-" + "_" + "." + "e"
+        return self in self.NUMBER
 
     @lru_cache(maxsize=None)
     def is_ws(self):  # type: () -> bool
         """
         Whether the character is a whitespace character or not.
         """
-        return self in " \t\r\n"
+        return self in self.WS
 
     @lru_cache(maxsize=None)
     def is_nl(self):  # type: () -> bool
         """
         Whether the character is a new line character or not.
         """
-        return self in "\n\r"
+        return self in self.NL
 
     @lru_cache(maxsize=None)
     def is_spaces(self):  # type: () -> bool
         """
         Whether the character is a space or not
         """
-        return self in " \t"
+        return self in self.SPACES


Test patch:
diff --git a/tests/examples/invalid/array_duplicate_comma.toml b/tests/examples/invalid/array_duplicate_comma.toml
new file mode 100644
index 0000000..02f25d7
--- /dev/null
+++ b/tests/examples/invalid/array_duplicate_comma.toml
@@ -0,0 +1,1 @@
+simple = [ 1 ,, 2 ]
diff --git a/tests/examples/invalid/array_leading_comma.toml b/tests/examples/invalid/array_leading_comma.toml
new file mode 100644
index 0000000..f05426f
--- /dev/null
+++ b/tests/examples/invalid/array_leading_comma.toml
@@ -0,0 +1,1 @@
+simple = [ , 1 ]
diff --git a/tests/examples/invalid/array_no_comma.toml b/tests/examples/invalid/array_no_comma.toml
new file mode 100644
index 0000000..ed0ccef
--- /dev/null
+++ b/tests/examples/invalid/array_no_comma.toml
@@ -0,0 +1,1 @@
+simple = [ 1 2 ]
diff --git a/tests/examples/invalid/inline_table_duplicate_comma.toml b/tests/examples/invalid/inline_table_duplicate_comma.toml
new file mode 100644
index 0000000..a1c30cb
--- /dev/null
+++ b/tests/examples/invalid/inline_table_duplicate_comma.toml
@@ -0,0 +1,1 @@
+simple = { a = 1 ,, b = 2}
diff --git a/tests/examples/invalid/inline_table_leading_comma.toml b/tests/examples/invalid/inline_table_leading_comma.toml
new file mode 100644
index 0000000..87b90a9
--- /dev/null
+++ b/tests/examples/invalid/inline_table_leading_comma.toml
@@ -0,0 +1,1 @@
+simple = { , a = 1 }
diff --git a/tests/examples/invalid/inline_table_no_comma.toml b/tests/examples/invalid/inline_table_no_comma.toml
new file mode 100644
index 0000000..74f71ba
--- /dev/null
+++ b/tests/examples/invalid/inline_table_no_comma.toml
@@ -0,0 +1,1 @@
+simple = { a = 1 b = 2}
diff --git a/tests/examples/invalid/inline_table_trailing_comma.toml b/tests/examples/invalid/inline_table_trailing_comma.toml
new file mode 100644
index 0000000..7c35069
--- /dev/null
+++ b/tests/examples/invalid/inline_table_trailing_comma.toml
@@ -0,0 +1,1 @@
+simple = { a = 1 , }
diff --git a/tests/examples/invalid/invalid_date.toml b/tests/examples/invalid/invalid_date.toml
new file mode 100644
index 0000000..eb04efd
--- /dev/null
+++ b/tests/examples/invalid/invalid_date.toml
@@ -0,0 +1,1 @@
+date = 2018-13-33
diff --git a/tests/examples/invalid/invalid_datetime.toml b/tests/examples/invalid/invalid_datetime.toml
new file mode 100644
index 0000000..c2bd978
--- /dev/null
+++ b/tests/examples/invalid/invalid_datetime.toml
@@ -0,0 +1,1 @@
+datetime = 2018-13-33T23:12:34
diff --git a/tests/examples/invalid/invalid_time.toml b/tests/examples/invalid/invalid_time.toml
new file mode 100644
index 0000000..f5862fa
--- /dev/null
+++ b/tests/examples/invalid/invalid_time.toml
@@ -0,0 +1,1 @@
+datetime = 26:61:61
diff --git a/tests/test_api.py b/tests/test_api.py
index 271b81a..86b589b 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -10,10 +10,14 @@ from datetime import time
 from tomlkit import dumps
 from tomlkit import loads
 from tomlkit import parse
-from tomlkit.exceptions import InvalidNumberOrDateError
+from tomlkit.exceptions import EmptyKeyError
+from tomlkit.exceptions import InvalidCharInStringError
+from tomlkit.exceptions import InvalidDateError
+from tomlkit.exceptions import InvalidDateTimeError
+from tomlkit.exceptions import InvalidTimeError
+from tomlkit.exceptions import InvalidNumberError
 from tomlkit.exceptions import MixedArrayTypesError
 from tomlkit.exceptions import UnexpectedCharError
-from tomlkit.exceptions import InvalidCharInStringError
 from tomlkit.items import AoT
 from tomlkit.items import Array
 from tomlkit.items import Bool
@@ -73,10 +77,20 @@ def test_parsed_document_are_properly_json_representable(
         ("key_value_with_trailing_chars", UnexpectedCharError),
         ("array_with_invalid_chars", UnexpectedCharError),
         ("mixed_array_types", MixedArrayTypesError),
-        ("invalid_number", InvalidNumberOrDateError),
+        ("invalid_number", InvalidNumberError),
+        ("invalid_date", InvalidDateError),
+        ("invalid_time", InvalidTimeError),
+        ("invalid_datetime", InvalidDateTimeError),
         ("trailing_comma", UnexpectedCharError),
         ("newline_in_singleline_string", InvalidCharInStringError),
         ("string_slash_whitespace_char", InvalidCharInStringError),
+        ("array_no_comma", UnexpectedCharError),
+        ("array_duplicate_comma", UnexpectedCharError),
+        ("array_leading_comma", UnexpectedCharError),
+        ("inline_table_no_comma", UnexpectedCharError),
+        ("inline_table_duplicate_comma", EmptyKeyError),
+        ("inline_table_leading_comma", EmptyKeyError),
+        ("inline_table_trailing_comma", UnexpectedCharError),
     ],
 )
 def test_parse_raises_errors_for_invalid_toml_files(
diff --git a/tests/test_parser.py b/tests/test_parser.py
new file mode 100644
index 0000000..d8219e1
--- /dev/null
+++ b/tests/test_parser.py
@@ -0,0 +1,15 @@
+import pytest
+
+from tomlkit.exceptions import InternalParserError
+from tomlkit.items import StringType
+from tomlkit.parser import Parser
+
+
+def test_parser_should_raise_an_internal_error_if_parsing_wrong_type_of_string():
+    parser = Parser('"foo"')
+
+    with pytest.raises(InternalParserError) as e:
+        parser._parse_string(StringType.SLL)
+
+    assert e.value.line == 1
+    assert e.value.col == 0
diff --git a/tests/test_toml_document.py b/tests/test_toml_document.py
index 6de0c22..2718af4 100644
--- a/tests/test_toml_document.py
+++ b/tests/test_toml_document.py
@@ -192,6 +192,25 @@ name = "Test 1"
     assert doc["foo"]["bar"]["tests"][0]["name"] == "Test 1"
 
 
+def test_document_with_new_sub_table_after_other_other_table():
+    content = """[foo]
+name = "Bar"
+
+[bar]
+name = "Baz"
+
+[foo.baz]
+name = "Test 1"
+"""
+
+    doc = parse(content)
+    assert doc["foo"]["name"] == "Bar"
+    assert doc["bar"]["name"] == "Baz"
+    assert doc["foo"]["baz"]["name"] == "Test 1"
+
+    assert doc.as_string() == content
+
+
 def test_inserting_after_element_with_no_new_line_adds_a_new_line():
     doc = parse("foo = 10")
     doc["bar"] = 11