Initial commit.
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..2b12baa
--- /dev/null
+++ b/test.py
@@ -0,0 +1,43 @@
+import toml, os, json, sys
+
+def _testbench_literal(type, text):
+    _type_table = {'str': 'string', 'int': 'integer'}
+    return {'type': _type_table.get(type, type), 'value': text}
+
+def _testbench_array(values):
+    return {'type': 'array', 'value': values}
+
+def _main():
+    succeeded = []
+    failed = []
+
+    for top, dirnames, fnames in os.walk('test'):
+        for fname in fnames:
+            if not fname.endswith('.toml'):
+                continue
+
+            try:
+                with open(os.path.join(top, fname), 'rb') as fin:
+                    parsed = toml.load(fin, _testbench_literal, _testbench_array)
+            except toml.TomlError:
+                parsed = None
+
+            try:
+                with open(os.path.join(top, fname[:-5] + '.json'), 'rb') as fin:
+                    bench = json.load(fin)
+            except IOError:
+                bench = None
+
+            if parsed != bench:
+                failed.append(fname)
+            else:
+                succeeded.append(fname)
+
+    for f in failed:
+        print 'failed: {f}'.format(f=f)
+    print 'succeeded: {succ}'.format(succ=len(succeeded))
+    return 1 if failed else 0
+
+if __name__ == '__main__':
+    sys.exit(_main())
+
diff --git a/test/invalid/array-mixed-types-arrays-and-ints.toml b/test/invalid/array-mixed-types-arrays-and-ints.toml
new file mode 100644
index 0000000..051ec73
--- /dev/null
+++ b/test/invalid/array-mixed-types-arrays-and-ints.toml
@@ -0,0 +1 @@
+arrays-and-ints =  [1, ["Arrays are not integers."]]
diff --git a/test/invalid/array-mixed-types-ints-and-floats.toml b/test/invalid/array-mixed-types-ints-and-floats.toml
new file mode 100644
index 0000000..a5aa9b7
--- /dev/null
+++ b/test/invalid/array-mixed-types-ints-and-floats.toml
@@ -0,0 +1 @@
+ints-and-floats = [1, 1.1]
diff --git a/test/invalid/array-mixed-types-strings-and-ints.toml b/test/invalid/array-mixed-types-strings-and-ints.toml
new file mode 100644
index 0000000..f348308
--- /dev/null
+++ b/test/invalid/array-mixed-types-strings-and-ints.toml
@@ -0,0 +1 @@
+strings-and-ints = ["hi", 42]
diff --git a/test/invalid/datetime-malformed-no-leads.toml b/test/invalid/datetime-malformed-no-leads.toml
new file mode 100644
index 0000000..123f173
--- /dev/null
+++ b/test/invalid/datetime-malformed-no-leads.toml
@@ -0,0 +1 @@
+no-leads = 1987-7-05T17:45:00Z
diff --git a/test/invalid/datetime-malformed-no-secs.toml b/test/invalid/datetime-malformed-no-secs.toml
new file mode 100644
index 0000000..ba93900
--- /dev/null
+++ b/test/invalid/datetime-malformed-no-secs.toml
@@ -0,0 +1 @@
+no-secs = 1987-07-05T17:45Z
diff --git a/test/invalid/datetime-malformed-no-t.toml b/test/invalid/datetime-malformed-no-t.toml
new file mode 100644
index 0000000..617e3c5
--- /dev/null
+++ b/test/invalid/datetime-malformed-no-t.toml
@@ -0,0 +1 @@
+no-t = 1987-07-0517:45:00Z
diff --git a/test/invalid/datetime-malformed-no-z.toml b/test/invalid/datetime-malformed-no-z.toml
new file mode 100644
index 0000000..cf66b1e
--- /dev/null
+++ b/test/invalid/datetime-malformed-no-z.toml
@@ -0,0 +1 @@
+no-z = 1987-07-05T17:45:00
diff --git a/test/invalid/datetime-malformed-with-milli.toml b/test/invalid/datetime-malformed-with-milli.toml
new file mode 100644
index 0000000..eef792f
--- /dev/null
+++ b/test/invalid/datetime-malformed-with-milli.toml
@@ -0,0 +1 @@
+with-milli = 1987-07-5T17:45:00.12Z
diff --git a/test/invalid/duplicate-key-table.toml b/test/invalid/duplicate-key-table.toml
new file mode 100644
index 0000000..cedf05f
--- /dev/null
+++ b/test/invalid/duplicate-key-table.toml
@@ -0,0 +1,5 @@
+[fruit]
+type = "apple"
+
+[fruit.type]
+apple = "yes"
diff --git a/test/invalid/duplicate-keys.toml b/test/invalid/duplicate-keys.toml
new file mode 100644
index 0000000..9b5aee0
--- /dev/null
+++ b/test/invalid/duplicate-keys.toml
@@ -0,0 +1,2 @@
+dupe = false
+dupe = true
diff --git a/test/invalid/duplicate-tables.toml b/test/invalid/duplicate-tables.toml
new file mode 100644
index 0000000..8ddf49b
--- /dev/null
+++ b/test/invalid/duplicate-tables.toml
@@ -0,0 +1,2 @@
+[a]
+[a]
diff --git a/test/invalid/empty-implicit-table.toml b/test/invalid/empty-implicit-table.toml
new file mode 100644
index 0000000..0cc36d0
--- /dev/null
+++ b/test/invalid/empty-implicit-table.toml
@@ -0,0 +1 @@
+[naughty..naughty]
diff --git a/test/invalid/empty-table.toml b/test/invalid/empty-table.toml
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/test/invalid/empty-table.toml
@@ -0,0 +1 @@
+[]
diff --git a/test/invalid/float-no-leading-zero.toml b/test/invalid/float-no-leading-zero.toml
new file mode 100644
index 0000000..cab76bf
--- /dev/null
+++ b/test/invalid/float-no-leading-zero.toml
@@ -0,0 +1,2 @@
+answer = .12345
+neganswer = -.12345
diff --git a/test/invalid/float-no-trailing-digits.toml b/test/invalid/float-no-trailing-digits.toml
new file mode 100644
index 0000000..cbff2d0
--- /dev/null
+++ b/test/invalid/float-no-trailing-digits.toml
@@ -0,0 +1,2 @@
+answer = 1.
+neganswer = -1.
diff --git a/test/invalid/key-empty.toml b/test/invalid/key-empty.toml
new file mode 100644
index 0000000..09f998f
--- /dev/null
+++ b/test/invalid/key-empty.toml
@@ -0,0 +1 @@
+ = 1
diff --git a/test/invalid/key-hash.toml b/test/invalid/key-hash.toml
new file mode 100644
index 0000000..e321b1f
--- /dev/null
+++ b/test/invalid/key-hash.toml
@@ -0,0 +1 @@
+a# = 1
diff --git a/test/invalid/key-newline.toml b/test/invalid/key-newline.toml
new file mode 100644
index 0000000..707aad5
--- /dev/null
+++ b/test/invalid/key-newline.toml
@@ -0,0 +1,2 @@
+a
+= 1
diff --git a/test/invalid/key-open-bracket.toml b/test/invalid/key-open-bracket.toml
new file mode 100644
index 0000000..f0aeb16
--- /dev/null
+++ b/test/invalid/key-open-bracket.toml
@@ -0,0 +1 @@
+[abc = 1
diff --git a/test/invalid/key-single-open-bracket.toml b/test/invalid/key-single-open-bracket.toml
new file mode 100644
index 0000000..8e2f0be
--- /dev/null
+++ b/test/invalid/key-single-open-bracket.toml
@@ -0,0 +1 @@
+[
\ No newline at end of file
diff --git a/test/invalid/key-space.toml b/test/invalid/key-space.toml
new file mode 100644
index 0000000..201806d
--- /dev/null
+++ b/test/invalid/key-space.toml
@@ -0,0 +1 @@
+a b = 1
\ No newline at end of file
diff --git a/test/invalid/key-start-bracket.toml b/test/invalid/key-start-bracket.toml
new file mode 100644
index 0000000..e0597ae
--- /dev/null
+++ b/test/invalid/key-start-bracket.toml
@@ -0,0 +1,3 @@
+[a]
+[xyz = 5
+[b]
diff --git a/test/invalid/key-two-equals.toml b/test/invalid/key-two-equals.toml
new file mode 100644
index 0000000..25a0378
--- /dev/null
+++ b/test/invalid/key-two-equals.toml
@@ -0,0 +1 @@
+key= = 1
diff --git a/test/invalid/llbrace.toml b/test/invalid/llbrace.toml
new file mode 100644
index 0000000..047978e
--- /dev/null
+++ b/test/invalid/llbrace.toml
@@ -0,0 +1 @@
+[ [table]]
diff --git a/test/invalid/rrbrace.toml b/test/invalid/rrbrace.toml
new file mode 100644
index 0000000..3a4dee4
--- /dev/null
+++ b/test/invalid/rrbrace.toml
@@ -0,0 +1 @@
+[[table] ]
diff --git a/test/invalid/string-bad-byte-escape.toml b/test/invalid/string-bad-byte-escape.toml
new file mode 100644
index 0000000..4c7be59
--- /dev/null
+++ b/test/invalid/string-bad-byte-escape.toml
@@ -0,0 +1 @@
+naughty = "\xAg"
diff --git a/test/invalid/string-bad-escape.toml b/test/invalid/string-bad-escape.toml
new file mode 100644
index 0000000..60acb0c
--- /dev/null
+++ b/test/invalid/string-bad-escape.toml
@@ -0,0 +1 @@
+invalid-escape = "This string has a bad \a escape character."
diff --git a/test/invalid/string-byte-escapes.toml b/test/invalid/string-byte-escapes.toml
new file mode 100644
index 0000000..e94452a
--- /dev/null
+++ b/test/invalid/string-byte-escapes.toml
@@ -0,0 +1 @@
+answer = "\x33"
diff --git a/test/invalid/string-no-close.toml b/test/invalid/string-no-close.toml
new file mode 100644
index 0000000..0c292fc
--- /dev/null
+++ b/test/invalid/string-no-close.toml
@@ -0,0 +1 @@
+no-ending-quote = "One time, at band camp
diff --git a/test/invalid/table-array-implicit.toml b/test/invalid/table-array-implicit.toml
new file mode 100644
index 0000000..05f2507
--- /dev/null
+++ b/test/invalid/table-array-implicit.toml
@@ -0,0 +1,14 @@
+# This test is a bit tricky. It should fail because the first use of
+# `[[albums.songs]]` without first declaring `albums` implies that `albums`
+# must be a table. The alternative would be quite weird. Namely, it wouldn't
+# comply with the TOML spec: "Each double-bracketed sub-table will belong to 
+# the most *recently* defined table element *above* it."
+#
+# This is in contrast to the *valid* test, table-array-implicit where
+# `[[albums.songs]]` works by itself, so long as `[[albums]]` isn't declared
+# later. (Although, `[albums]` could be.)
+[[albums.songs]]
+name = "Glory Days"
+
+[[albums]]
+name = "Born in the USA"
diff --git a/test/invalid/table-array-malformed-bracket.toml b/test/invalid/table-array-malformed-bracket.toml
new file mode 100644
index 0000000..39c73b0
--- /dev/null
+++ b/test/invalid/table-array-malformed-bracket.toml
@@ -0,0 +1,2 @@
+[[albums]
+name = "Born to Run"
diff --git a/test/invalid/table-array-malformed-empty.toml b/test/invalid/table-array-malformed-empty.toml
new file mode 100644
index 0000000..a470ca3
--- /dev/null
+++ b/test/invalid/table-array-malformed-empty.toml
@@ -0,0 +1,2 @@
+[[]]
+name = "Born to Run"
diff --git a/test/invalid/table-empty.toml b/test/invalid/table-empty.toml
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/test/invalid/table-empty.toml
@@ -0,0 +1 @@
+[]
diff --git a/test/invalid/table-nested-brackets-close.toml b/test/invalid/table-nested-brackets-close.toml
new file mode 100644
index 0000000..c8b5a67
--- /dev/null
+++ b/test/invalid/table-nested-brackets-close.toml
@@ -0,0 +1,2 @@
+[a]b]
+zyx = 42
diff --git a/test/invalid/table-nested-brackets-open.toml b/test/invalid/table-nested-brackets-open.toml
new file mode 100644
index 0000000..246d7e9
--- /dev/null
+++ b/test/invalid/table-nested-brackets-open.toml
@@ -0,0 +1,2 @@
+[a[b]
+zyx = 42
diff --git a/test/invalid/table-whitespace.toml b/test/invalid/table-whitespace.toml
new file mode 100644
index 0000000..79bbcb1
--- /dev/null
+++ b/test/invalid/table-whitespace.toml
@@ -0,0 +1 @@
+[invalid key]
\ No newline at end of file
diff --git a/test/invalid/table-with-pound.toml b/test/invalid/table-with-pound.toml
new file mode 100644
index 0000000..0d8edb5
--- /dev/null
+++ b/test/invalid/table-with-pound.toml
@@ -0,0 +1,2 @@
+[key#group]
+answer = 42
\ No newline at end of file
diff --git a/test/invalid/text-after-array-entries.toml b/test/invalid/text-after-array-entries.toml
new file mode 100644
index 0000000..1a72890
--- /dev/null
+++ b/test/invalid/text-after-array-entries.toml
@@ -0,0 +1,4 @@
+array = [
+  "Is there life after an array separator?", No
+  "Entry"
+]
diff --git a/test/invalid/text-after-integer.toml b/test/invalid/text-after-integer.toml
new file mode 100644
index 0000000..42de7af
--- /dev/null
+++ b/test/invalid/text-after-integer.toml
@@ -0,0 +1 @@
+answer = 42 the ultimate answer?
diff --git a/test/invalid/text-after-string.toml b/test/invalid/text-after-string.toml
new file mode 100644
index 0000000..c92a6f1
--- /dev/null
+++ b/test/invalid/text-after-string.toml
@@ -0,0 +1 @@
+string = "Is there life after strings?" No.
diff --git a/test/invalid/text-after-table.toml b/test/invalid/text-after-table.toml
new file mode 100644
index 0000000..87da9db
--- /dev/null
+++ b/test/invalid/text-after-table.toml
@@ -0,0 +1 @@
+[error] this shouldn't be here
diff --git a/test/invalid/text-after-table2.toml b/test/invalid/text-after-table2.toml
new file mode 100644
index 0000000..6886784
--- /dev/null
+++ b/test/invalid/text-after-table2.toml
@@ -0,0 +1 @@
+[error] this = "should not be here"
diff --git a/test/invalid/text-before-array-separator.toml b/test/invalid/text-before-array-separator.toml
new file mode 100644
index 0000000..9b06a39
--- /dev/null
+++ b/test/invalid/text-before-array-separator.toml
@@ -0,0 +1,4 @@
+array = [
+  "Is there life before an array separator?" No,
+  "Entry"
+]
diff --git a/test/invalid/text-in-array.toml b/test/invalid/text-in-array.toml
new file mode 100644
index 0000000..a6a6c42
--- /dev/null
+++ b/test/invalid/text-in-array.toml
@@ -0,0 +1,5 @@
+array = [
+  "Entry 1",
+  I don't belong,
+  "Entry 2",
+]
diff --git a/test/valid/array-empty.json b/test/valid/array-empty.json
new file mode 100644
index 0000000..2fbf256
--- /dev/null
+++ b/test/valid/array-empty.json
@@ -0,0 +1,11 @@
+{
+    "thevoid": { "type": "array", "value": [
+        {"type": "array", "value": [
+            {"type": "array", "value": [
+                {"type": "array", "value": [
+                    {"type": "array", "value": []}
+                ]}
+            ]}
+        ]}
+    ]}
+}
diff --git a/test/valid/array-empty.toml b/test/valid/array-empty.toml
new file mode 100644
index 0000000..fa58dc6
--- /dev/null
+++ b/test/valid/array-empty.toml
@@ -0,0 +1 @@
+thevoid = [[[[[]]]]]
diff --git a/test/valid/array-nospaces.json b/test/valid/array-nospaces.json
new file mode 100644
index 0000000..1833d61
--- /dev/null
+++ b/test/valid/array-nospaces.json
@@ -0,0 +1,10 @@
+{
+    "ints": {
+        "type": "array",
+        "value": [
+            {"type": "integer", "value": "1"},
+            {"type": "integer", "value": "2"},
+            {"type": "integer", "value": "3"}
+        ]
+    }
+}
diff --git a/test/valid/array-nospaces.toml b/test/valid/array-nospaces.toml
new file mode 100644
index 0000000..6618936
--- /dev/null
+++ b/test/valid/array-nospaces.toml
@@ -0,0 +1 @@
+ints = [1,2,3]
diff --git a/test/valid/arrays-hetergeneous.json b/test/valid/arrays-hetergeneous.json
new file mode 100644
index 0000000..478fa5c
--- /dev/null
+++ b/test/valid/arrays-hetergeneous.json
@@ -0,0 +1,19 @@
+{
+    "mixed": {
+        "type": "array",
+        "value": [
+            {"type": "array", "value": [
+                {"type": "integer", "value": "1"},
+                {"type": "integer", "value": "2"}
+            ]},
+            {"type": "array", "value": [
+                {"type": "string", "value": "a"},
+                {"type": "string", "value": "b"}
+            ]},
+            {"type": "array", "value": [
+                {"type": "float", "value": "1.1"},
+                {"type": "float", "value": "2.1"}
+            ]}
+        ]
+    }
+}
diff --git a/test/valid/arrays-hetergeneous.toml b/test/valid/arrays-hetergeneous.toml
new file mode 100644
index 0000000..a246fcf
--- /dev/null
+++ b/test/valid/arrays-hetergeneous.toml
@@ -0,0 +1 @@
+mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]
diff --git a/test/valid/arrays-nested.json b/test/valid/arrays-nested.json
new file mode 100644
index 0000000..d21920c
--- /dev/null
+++ b/test/valid/arrays-nested.json
@@ -0,0 +1,13 @@
+{
+    "nest": {
+        "type": "array",
+        "value": [
+            {"type": "array", "value": [
+                {"type": "string", "value": "a"}
+            ]},
+            {"type": "array", "value": [
+                {"type": "string", "value": "b"}
+            ]}
+        ]
+    }
+}
diff --git a/test/valid/arrays-nested.toml b/test/valid/arrays-nested.toml
new file mode 100644
index 0000000..ce33022
--- /dev/null
+++ b/test/valid/arrays-nested.toml
@@ -0,0 +1 @@
+nest = [["a"], ["b"]]
diff --git a/test/valid/arrays.json b/test/valid/arrays.json
new file mode 100644
index 0000000..58aedbc
--- /dev/null
+++ b/test/valid/arrays.json
@@ -0,0 +1,34 @@
+{
+    "ints": {
+        "type": "array",
+        "value": [
+            {"type": "integer", "value": "1"},
+            {"type": "integer", "value": "2"},
+            {"type": "integer", "value": "3"}
+        ]
+    },
+    "floats": {
+        "type": "array",
+        "value": [
+            {"type": "float", "value": "1.1"},
+            {"type": "float", "value": "2.1"},
+            {"type": "float", "value": "3.1"}
+        ]
+    },
+    "strings": {
+        "type": "array",
+        "value": [
+            {"type": "string", "value": "a"},
+            {"type": "string", "value": "b"},
+            {"type": "string", "value": "c"}
+        ]
+    },
+    "dates": {
+        "type": "array",
+        "value": [
+            {"type": "datetime", "value": "1987-07-05T17:45:00Z"},
+            {"type": "datetime", "value": "1979-05-27T07:32:00Z"},
+            {"type": "datetime", "value": "2006-06-01T11:00:00Z"}
+        ]
+    }
+}
diff --git a/test/valid/arrays.toml b/test/valid/arrays.toml
new file mode 100644
index 0000000..c435f57
--- /dev/null
+++ b/test/valid/arrays.toml
@@ -0,0 +1,8 @@
+ints = [1, 2, 3]
+floats = [1.1, 2.1, 3.1]
+strings = ["a", "b", "c"]
+dates = [
+  1987-07-05T17:45:00Z,
+  1979-05-27T07:32:00Z,
+  2006-06-01T11:00:00Z,
+]
diff --git a/test/valid/bool.json b/test/valid/bool.json
new file mode 100644
index 0000000..ae368e9
--- /dev/null
+++ b/test/valid/bool.json
@@ -0,0 +1,4 @@
+{
+    "f": {"type": "bool", "value": "false"},
+    "t": {"type": "bool", "value": "true"}
+}
diff --git a/test/valid/bool.toml b/test/valid/bool.toml
new file mode 100644
index 0000000..a8a829b
--- /dev/null
+++ b/test/valid/bool.toml
@@ -0,0 +1,2 @@
+t = true
+f = false
diff --git a/test/valid/comments-everywhere.json b/test/valid/comments-everywhere.json
new file mode 100644
index 0000000..e69a2e9
--- /dev/null
+++ b/test/valid/comments-everywhere.json
@@ -0,0 +1,12 @@
+{
+    "group": {
+        "answer": {"type": "integer", "value": "42"},
+        "more": {
+            "type": "array",
+            "value": [
+                {"type": "integer", "value": "42"},
+                {"type": "integer", "value": "42"}
+            ]
+        }
+    }
+}
diff --git a/test/valid/comments-everywhere.toml b/test/valid/comments-everywhere.toml
new file mode 100644
index 0000000..3dca74c
--- /dev/null
+++ b/test/valid/comments-everywhere.toml
@@ -0,0 +1,24 @@
+# Top comment.
+  # Top comment.
+# Top comment.
+
+# [no-extraneous-groups-please]
+
+[group] # Comment
+answer = 42 # Comment
+# no-extraneous-keys-please = 999
+# Inbetween comment.
+more = [ # Comment
+  # What about multiple # comments?
+  # Can you handle it?
+  #
+          # Evil.
+# Evil.
+  42, 42, # Comments within arrays are fun.
+  # What about multiple # comments?
+  # Can you handle it?
+  #
+          # Evil.
+# Evil.
+# ] Did I fool you?
+] # Hopefully not.
diff --git a/test/valid/datetime.json b/test/valid/datetime.json
new file mode 100644
index 0000000..2ca93ce
--- /dev/null
+++ b/test/valid/datetime.json
@@ -0,0 +1,3 @@
+{
+    "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}
+}
diff --git a/test/valid/datetime.toml b/test/valid/datetime.toml
new file mode 100644
index 0000000..2e99340
--- /dev/null
+++ b/test/valid/datetime.toml
@@ -0,0 +1 @@
+bestdayever = 1987-07-05T17:45:00Z
diff --git a/test/valid/empty.json b/test/valid/empty.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/test/valid/empty.json
@@ -0,0 +1 @@
+{}
diff --git a/test/valid/empty.toml b/test/valid/empty.toml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/valid/empty.toml
diff --git a/test/valid/example.json b/test/valid/example.json
new file mode 100644
index 0000000..48aa907
--- /dev/null
+++ b/test/valid/example.json
@@ -0,0 +1,14 @@
+{
+  "best-day-ever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"},
+  "numtheory": {
+    "boring": {"type": "bool", "value": "false"},
+    "perfection": {
+      "type": "array",
+      "value": [
+        {"type": "integer", "value": "6"},
+        {"type": "integer", "value": "28"},
+        {"type": "integer", "value": "496"}
+      ]
+    }
+  }
+}
diff --git a/test/valid/example.toml b/test/valid/example.toml
new file mode 100644
index 0000000..8cb02e0
--- /dev/null
+++ b/test/valid/example.toml
@@ -0,0 +1,5 @@
+best-day-ever = 1987-07-05T17:45:00Z
+
+[numtheory]
+boring = false
+perfection = [6, 28, 496]
diff --git a/test/valid/float.json b/test/valid/float.json
new file mode 100644
index 0000000..b8a2e97
--- /dev/null
+++ b/test/valid/float.json
@@ -0,0 +1,4 @@
+{
+    "pi": {"type": "float", "value": "3.14"},
+    "negpi": {"type": "float", "value": "-3.14"}
+}
diff --git a/test/valid/float.toml b/test/valid/float.toml
new file mode 100644
index 0000000..7c528d2
--- /dev/null
+++ b/test/valid/float.toml
@@ -0,0 +1,2 @@
+pi = 3.14
+negpi = -3.14
diff --git a/test/valid/implicit-and-explicit-after.json b/test/valid/implicit-and-explicit-after.json
new file mode 100644
index 0000000..374bd09
--- /dev/null
+++ b/test/valid/implicit-and-explicit-after.json
@@ -0,0 +1,10 @@
+{
+    "a": {
+        "better": {"type": "integer", "value": "43"},
+        "b": {
+            "c": {
+                "answer": {"type": "integer", "value": "42"}
+            }
+        }
+    }
+}
diff --git a/test/valid/implicit-and-explicit-after.toml b/test/valid/implicit-and-explicit-after.toml
new file mode 100644
index 0000000..c0e8865
--- /dev/null
+++ b/test/valid/implicit-and-explicit-after.toml
@@ -0,0 +1,5 @@
+[a.b.c]
+answer = 42
+
+[a]
+better = 43
diff --git a/test/valid/implicit-and-explicit-before.json b/test/valid/implicit-and-explicit-before.json
new file mode 100644
index 0000000..374bd09
--- /dev/null
+++ b/test/valid/implicit-and-explicit-before.json
@@ -0,0 +1,10 @@
+{
+    "a": {
+        "better": {"type": "integer", "value": "43"},
+        "b": {
+            "c": {
+                "answer": {"type": "integer", "value": "42"}
+            }
+        }
+    }
+}
diff --git a/test/valid/implicit-and-explicit-before.toml b/test/valid/implicit-and-explicit-before.toml
new file mode 100644
index 0000000..eee68ff
--- /dev/null
+++ b/test/valid/implicit-and-explicit-before.toml
@@ -0,0 +1,5 @@
+[a]
+better = 43
+
+[a.b.c]
+answer = 42
diff --git a/test/valid/implicit-groups.json b/test/valid/implicit-groups.json
new file mode 100644
index 0000000..fbae7fc
--- /dev/null
+++ b/test/valid/implicit-groups.json
@@ -0,0 +1,9 @@
+{
+    "a": {
+        "b": {
+            "c": {
+                "answer": {"type": "integer", "value": "42"}
+            }
+        }
+    }
+}
diff --git a/test/valid/implicit-groups.toml b/test/valid/implicit-groups.toml
new file mode 100644
index 0000000..b6333e4
--- /dev/null
+++ b/test/valid/implicit-groups.toml
@@ -0,0 +1,2 @@
+[a.b.c]
+answer = 42
diff --git a/test/valid/inline-table.json b/test/valid/inline-table.json
new file mode 100644
index 0000000..6172b18
--- /dev/null
+++ b/test/valid/inline-table.json
@@ -0,0 +1,11 @@
+{
+    "simple": { "a": {"type": "integer", "value": "1"} },
+    "str-key": { "a": {"type": "integer", "value": "1"} },
+    "table-array": {
+        "type": "array",
+        "value": [
+            { "a": {"type": "integer", "value": "1"} },
+            { "b": {"type": "integer", "value": "2"} }
+        ]
+    }
+}
diff --git a/test/valid/inline-table.toml b/test/valid/inline-table.toml
new file mode 100644
index 0000000..424b304
--- /dev/null
+++ b/test/valid/inline-table.toml
@@ -0,0 +1,3 @@
+simple = { a = 1 }
+str-key = { "a" = 1 }
+table-array = [{ "a" = 1 }, { "b" = 2 }]
diff --git a/test/valid/integer.json b/test/valid/integer.json
new file mode 100644
index 0000000..61985a1
--- /dev/null
+++ b/test/valid/integer.json
@@ -0,0 +1,4 @@
+{
+    "answer": {"type": "integer", "value": "42"},
+    "neganswer": {"type": "integer", "value": "-42"}
+}
diff --git a/test/valid/integer.toml b/test/valid/integer.toml
new file mode 100644
index 0000000..c4f6297
--- /dev/null
+++ b/test/valid/integer.toml
@@ -0,0 +1,2 @@
+answer = 42
+neganswer = -42
diff --git a/test/valid/key-equals-nospace.json b/test/valid/key-equals-nospace.json
new file mode 100644
index 0000000..1f8709a
--- /dev/null
+++ b/test/valid/key-equals-nospace.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "integer", "value": "42"}
+}
diff --git a/test/valid/key-equals-nospace.toml b/test/valid/key-equals-nospace.toml
new file mode 100644
index 0000000..560901c
--- /dev/null
+++ b/test/valid/key-equals-nospace.toml
@@ -0,0 +1 @@
+answer=42
diff --git a/test/valid/key-space.json b/test/valid/key-space.json
new file mode 100644
index 0000000..9d1f769
--- /dev/null
+++ b/test/valid/key-space.json
@@ -0,0 +1,3 @@
+{
+    "a b": {"type": "integer", "value": "1"}
+}
diff --git a/test/valid/key-space.toml b/test/valid/key-space.toml
new file mode 100644
index 0000000..f4f36c4
--- /dev/null
+++ b/test/valid/key-space.toml
@@ -0,0 +1 @@
+"a b" = 1
diff --git a/test/valid/key-special-chars.json b/test/valid/key-special-chars.json
new file mode 100644
index 0000000..3585b2c
--- /dev/null
+++ b/test/valid/key-special-chars.json
@@ -0,0 +1,5 @@
+{
+    "~!@$^&*()_+-`1234567890[]|/?><.,;:'": {
+        "type": "integer", "value": "1"
+    }
+}
diff --git a/test/valid/key-special-chars.toml b/test/valid/key-special-chars.toml
new file mode 100644
index 0000000..cc572be
--- /dev/null
+++ b/test/valid/key-special-chars.toml
@@ -0,0 +1 @@
+"~!@$^&*()_+-`1234567890[]|/?><.,;:'" = 1
diff --git a/test/valid/long-float.json b/test/valid/long-float.json
new file mode 100644
index 0000000..8ceed47
--- /dev/null
+++ b/test/valid/long-float.json
@@ -0,0 +1,4 @@
+{
+    "longpi": {"type": "float", "value": "3.141592653589793"},
+    "neglongpi": {"type": "float", "value": "-3.141592653589793"}
+}
diff --git a/test/valid/long-float.toml b/test/valid/long-float.toml
new file mode 100644
index 0000000..9558ae4
--- /dev/null
+++ b/test/valid/long-float.toml
@@ -0,0 +1,2 @@
+longpi = 3.141592653589793
+neglongpi = -3.141592653589793
diff --git a/test/valid/long-integer.json b/test/valid/long-integer.json
new file mode 100644
index 0000000..16c331e
--- /dev/null
+++ b/test/valid/long-integer.json
@@ -0,0 +1,4 @@
+{
+    "answer": {"type": "integer", "value": "9223372036854775807"},
+    "neganswer": {"type": "integer", "value": "-9223372036854775808"}
+}
diff --git a/test/valid/long-integer.toml b/test/valid/long-integer.toml
new file mode 100644
index 0000000..424a13a
--- /dev/null
+++ b/test/valid/long-integer.toml
@@ -0,0 +1,2 @@
+answer = 9223372036854775807
+neganswer = -9223372036854775808
diff --git a/test/valid/multiline-string.json b/test/valid/multiline-string.json
new file mode 100644
index 0000000..075bf50
--- /dev/null
+++ b/test/valid/multiline-string.json
@@ -0,0 +1,30 @@
+{
+    "multiline_empty_one": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline_empty_two": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline_empty_three": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline_empty_four": {
+        "type": "string",
+        "value": ""
+    },
+    "equivalent_one": {
+        "type": "string",
+        "value": "The quick brown fox jumps over the lazy dog."
+    },
+    "equivalent_two": {
+        "type": "string",
+        "value": "The quick brown fox jumps over the lazy dog."
+    },
+    "equivalent_three": {
+        "type": "string",
+        "value": "The quick brown fox jumps over the lazy dog."
+    }
+}
diff --git a/test/valid/multiline-string.toml b/test/valid/multiline-string.toml
new file mode 100644
index 0000000..15b1143
--- /dev/null
+++ b/test/valid/multiline-string.toml
@@ -0,0 +1,23 @@
+multiline_empty_one = """"""
+multiline_empty_two = """
+"""
+multiline_empty_three = """\
+    """
+multiline_empty_four = """\
+   \
+   \
+   """
+
+equivalent_one = "The quick brown fox jumps over the lazy dog."
+equivalent_two = """
+The quick brown \
+
+
+  fox jumps over \
+    the lazy dog."""
+
+equivalent_three = """\
+       The quick brown \
+       fox jumps over \
+       the lazy dog.\
+       """
diff --git a/test/valid/raw-multiline-string.json b/test/valid/raw-multiline-string.json
new file mode 100644
index 0000000..b43cce5
--- /dev/null
+++ b/test/valid/raw-multiline-string.json
@@ -0,0 +1,14 @@
+{
+    "oneline": {
+        "type": "string",
+        "value": "This string has a ' quote character."
+    },
+    "firstnl": {
+        "type": "string",
+        "value": "This string has a ' quote character."
+    },
+    "multiline": {
+        "type": "string",
+        "value": "This string\nhas ' a quote character\nand more than\none newline\nin it."
+    }
+}
diff --git a/test/valid/raw-multiline-string.toml b/test/valid/raw-multiline-string.toml
new file mode 100644
index 0000000..8094c03
--- /dev/null
+++ b/test/valid/raw-multiline-string.toml
@@ -0,0 +1,9 @@
+oneline = '''This string has a ' quote character.'''
+firstnl = '''
+This string has a ' quote character.'''
+multiline = '''
+This string
+has ' a quote character
+and more than
+one newline
+in it.'''
diff --git a/test/valid/raw-string.json b/test/valid/raw-string.json
new file mode 100644
index 0000000..693ab9b
--- /dev/null
+++ b/test/valid/raw-string.json
@@ -0,0 +1,30 @@
+{
+    "backspace": {
+        "type": "string",
+        "value": "This string has a \\b backspace character."
+    },
+    "tab": {
+        "type": "string",
+        "value": "This string has a \\t tab character."
+    },
+    "newline": {
+        "type": "string",
+        "value": "This string has a \\n new line character."
+    },
+    "formfeed": {
+        "type": "string",
+        "value": "This string has a \\f form feed character."
+    },
+    "carriage": {
+        "type": "string",
+        "value": "This string has a \\r carriage return character."
+    },
+    "slash": {
+        "type": "string",
+        "value": "This string has a \\/ slash character."
+    },
+    "backslash": {
+        "type": "string",
+        "value": "This string has a \\\\ backslash character."
+    }
+}
diff --git a/test/valid/raw-string.toml b/test/valid/raw-string.toml
new file mode 100644
index 0000000..92acd25
--- /dev/null
+++ b/test/valid/raw-string.toml
@@ -0,0 +1,7 @@
+backspace = 'This string has a \b backspace character.'
+tab = 'This string has a \t tab character.'
+newline = 'This string has a \n new line character.'
+formfeed = 'This string has a \f form feed character.'
+carriage = 'This string has a \r carriage return character.'
+slash = 'This string has a \/ slash character.'
+backslash = 'This string has a \\ backslash character.'
diff --git a/test/valid/string-empty.json b/test/valid/string-empty.json
new file mode 100644
index 0000000..6c26d69
--- /dev/null
+++ b/test/valid/string-empty.json
@@ -0,0 +1,6 @@
+{
+    "answer": {
+        "type": "string",
+        "value": ""
+    }
+}
diff --git a/test/valid/string-empty.toml b/test/valid/string-empty.toml
new file mode 100644
index 0000000..e37e681
--- /dev/null
+++ b/test/valid/string-empty.toml
@@ -0,0 +1 @@
+answer = ""
diff --git a/test/valid/string-escapes.json b/test/valid/string-escapes.json
new file mode 100644
index 0000000..98e2c82
--- /dev/null
+++ b/test/valid/string-escapes.json
@@ -0,0 +1,46 @@
+{
+    "backspace": {
+        "type": "string",
+        "value": "This string has a \u0008 backspace character."
+    },
+    "tab": {
+        "type": "string",
+        "value": "This string has a \u0009 tab character."
+    },
+    "newline": {
+        "type": "string",
+        "value": "This string has a \u000A new line character."
+    },
+    "formfeed": {
+        "type": "string",
+        "value": "This string has a \u000C form feed character."
+    },
+    "carriage": {
+        "type": "string",
+        "value": "This string has a \u000D carriage return character."
+    },
+    "quote": {
+        "type": "string",
+        "value": "This string has a \u0022 quote character."
+    },
+    "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/valid/string-escapes.toml b/test/valid/string-escapes.toml
new file mode 100644
index 0000000..6d554e4
--- /dev/null
+++ b/test/valid/string-escapes.toml
@@ -0,0 +1,11 @@
+backspace = "This string has a \b backspace character."
+tab = "This string has a \t tab character."
+newline = "This string has a \n new line character."
+formfeed = "This string has a \f form feed character."
+carriage = "This string has a \r carriage return character."
+quote = "This string has a \" quote character."
+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/valid/string-simple.json b/test/valid/string-simple.json
new file mode 100644
index 0000000..2e05f99
--- /dev/null
+++ b/test/valid/string-simple.json
@@ -0,0 +1,6 @@
+{
+    "answer": {
+        "type": "string",
+        "value": "You are not drinking enough whisky."
+    }
+}
diff --git a/test/valid/string-simple.toml b/test/valid/string-simple.toml
new file mode 100644
index 0000000..e17ade6
--- /dev/null
+++ b/test/valid/string-simple.toml
@@ -0,0 +1 @@
+answer = "You are not drinking enough whisky."
diff --git a/test/valid/string-with-pound.json b/test/valid/string-with-pound.json
new file mode 100644
index 0000000..33cdc9c
--- /dev/null
+++ b/test/valid/string-with-pound.json
@@ -0,0 +1,7 @@
+{
+    "pound": {"type": "string", "value": "We see no # comments here."},
+    "poundcomment": {
+        "type": "string",
+        "value": "But there are # some comments here."
+    }
+}
diff --git a/test/valid/string-with-pound.toml b/test/valid/string-with-pound.toml
new file mode 100644
index 0000000..5fd8746
--- /dev/null
+++ b/test/valid/string-with-pound.toml
@@ -0,0 +1,2 @@
+pound = "We see no # comments here."
+poundcomment = "But there are # some comments here." # Did I # mess you up?
diff --git a/test/valid/table-array-implicit.json b/test/valid/table-array-implicit.json
new file mode 100644
index 0000000..32e4640
--- /dev/null
+++ b/test/valid/table-array-implicit.json
@@ -0,0 +1,7 @@
+{
+    "albums": {
+       "songs": [
+           {"name": {"type": "string", "value": "Glory Days"}}
+       ]
+    }
+}
diff --git a/test/valid/table-array-implicit.toml b/test/valid/table-array-implicit.toml
new file mode 100644
index 0000000..3157ac9
--- /dev/null
+++ b/test/valid/table-array-implicit.toml
@@ -0,0 +1,2 @@
+[[albums.songs]]
+name = "Glory Days"
diff --git a/test/valid/table-array-many.json b/test/valid/table-array-many.json
new file mode 100644
index 0000000..84df2da
--- /dev/null
+++ b/test/valid/table-array-many.json
@@ -0,0 +1,16 @@
+{
+    "people": [
+        {
+            "first_name": {"type": "string", "value": "Bruce"},
+            "last_name": {"type": "string", "value": "Springsteen"}
+        },
+        {
+            "first_name": {"type": "string", "value": "Eric"},
+            "last_name": {"type": "string", "value": "Clapton"}
+        },
+        {
+            "first_name": {"type": "string", "value": "Bob"},
+            "last_name": {"type": "string", "value": "Seger"}
+        }
+    ]
+}
diff --git a/test/valid/table-array-many.toml b/test/valid/table-array-many.toml
new file mode 100644
index 0000000..46062be
--- /dev/null
+++ b/test/valid/table-array-many.toml
@@ -0,0 +1,11 @@
+[[people]]
+first_name = "Bruce"
+last_name = "Springsteen"
+
+[[people]]
+first_name = "Eric"
+last_name = "Clapton"
+
+[[people]]
+first_name = "Bob"
+last_name = "Seger"
diff --git a/test/valid/table-array-nest.json b/test/valid/table-array-nest.json
new file mode 100644
index 0000000..c117afa
--- /dev/null
+++ b/test/valid/table-array-nest.json
@@ -0,0 +1,18 @@
+{
+    "albums": [
+        {
+            "name": {"type": "string", "value": "Born to Run"},
+            "songs": [
+                {"name": {"type": "string", "value": "Jungleland"}},
+                {"name": {"type": "string", "value": "Meeting Across the River"}}
+            ]
+        },
+        {
+            "name": {"type": "string", "value": "Born in the USA"},
+            "songs": [
+                {"name": {"type": "string", "value": "Glory Days"}},
+                {"name": {"type": "string", "value": "Dancing in the Dark"}}
+            ]
+        }
+    ]
+}
diff --git a/test/valid/table-array-nest.toml b/test/valid/table-array-nest.toml
new file mode 100644
index 0000000..d659a3d
--- /dev/null
+++ b/test/valid/table-array-nest.toml
@@ -0,0 +1,17 @@
+[[albums]]
+name = "Born to Run"
+
+  [[albums.songs]]
+  name = "Jungleland"
+
+  [[albums.songs]]
+  name = "Meeting Across the River"
+
+[[albums]]
+name = "Born in the USA"
+  
+  [[albums.songs]]
+  name = "Glory Days"
+
+  [[albums.songs]]
+  name = "Dancing in the Dark"
diff --git a/test/valid/table-array-one.json b/test/valid/table-array-one.json
new file mode 100644
index 0000000..d75faae
--- /dev/null
+++ b/test/valid/table-array-one.json
@@ -0,0 +1,8 @@
+{
+    "people": [
+        {
+            "first_name": {"type": "string", "value": "Bruce"},
+            "last_name": {"type": "string", "value": "Springsteen"}
+        }
+    ]
+}
diff --git a/test/valid/table-array-one.toml b/test/valid/table-array-one.toml
new file mode 100644
index 0000000..cd7e1b6
--- /dev/null
+++ b/test/valid/table-array-one.toml
@@ -0,0 +1,3 @@
+[[people]]
+first_name = "Bruce"
+last_name = "Springsteen"
diff --git a/test/valid/table-empty.json b/test/valid/table-empty.json
new file mode 100644
index 0000000..6f3873a
--- /dev/null
+++ b/test/valid/table-empty.json
@@ -0,0 +1,3 @@
+{
+    "a": {}
+}
diff --git a/test/valid/table-empty.toml b/test/valid/table-empty.toml
new file mode 100644
index 0000000..8bb6a0a
--- /dev/null
+++ b/test/valid/table-empty.toml
@@ -0,0 +1 @@
+[a]
diff --git a/test/valid/table-no-eol.json b/test/valid/table-no-eol.json
new file mode 100644
index 0000000..11fa444
--- /dev/null
+++ b/test/valid/table-no-eol.json
@@ -0,0 +1 @@
+{ "table": {} }
diff --git a/test/valid/table-no-eol.toml b/test/valid/table-no-eol.toml
new file mode 100644
index 0000000..741b2d1
--- /dev/null
+++ b/test/valid/table-no-eol.toml
@@ -0,0 +1 @@
+[table]
\ No newline at end of file
diff --git a/test/valid/table-sub-empty.json b/test/valid/table-sub-empty.json
new file mode 100644
index 0000000..9787770
--- /dev/null
+++ b/test/valid/table-sub-empty.json
@@ -0,0 +1,3 @@
+{
+    "a": { "b": {} }
+}
diff --git a/test/valid/table-sub-empty.toml b/test/valid/table-sub-empty.toml
new file mode 100644
index 0000000..70b7fe1
--- /dev/null
+++ b/test/valid/table-sub-empty.toml
@@ -0,0 +1,2 @@
+[a]
+[a.b]
diff --git a/test/valid/table-whitespace.json b/test/valid/table-whitespace.json
new file mode 100644
index 0000000..3a73ec8
--- /dev/null
+++ b/test/valid/table-whitespace.json
@@ -0,0 +1,3 @@
+{
+    "valid key": {}
+}
diff --git a/test/valid/table-whitespace.toml b/test/valid/table-whitespace.toml
new file mode 100644
index 0000000..daf881d
--- /dev/null
+++ b/test/valid/table-whitespace.toml
@@ -0,0 +1 @@
+["valid key"]
diff --git a/test/valid/table-with-pound.json b/test/valid/table-with-pound.json
new file mode 100644
index 0000000..5e594e4
--- /dev/null
+++ b/test/valid/table-with-pound.json
@@ -0,0 +1,5 @@
+{
+    "key#group": {
+        "answer": {"type": "integer", "value": "42"}
+    }
+}
diff --git a/test/valid/table-with-pound.toml b/test/valid/table-with-pound.toml
new file mode 100644
index 0000000..33f2c4f
--- /dev/null
+++ b/test/valid/table-with-pound.toml
@@ -0,0 +1,2 @@
+["key#group"]
+answer = 42
diff --git a/test/valid/unicode-escape.json b/test/valid/unicode-escape.json
new file mode 100644
index 0000000..216f8f7
--- /dev/null
+++ b/test/valid/unicode-escape.json
@@ -0,0 +1,4 @@
+{
+    "answer4": {"type": "string", "value": "\u03B4"},
+    "answer8": {"type": "string", "value": "\u03B4"}
+}
diff --git a/test/valid/unicode-escape.toml b/test/valid/unicode-escape.toml
new file mode 100644
index 0000000..82faecb
--- /dev/null
+++ b/test/valid/unicode-escape.toml
@@ -0,0 +1,2 @@
+answer4 = "\u03B4"
+answer8 = "\U000003B4"
diff --git a/test/valid/unicode-literal.json b/test/valid/unicode-literal.json
new file mode 100644
index 0000000..00aa2f8
--- /dev/null
+++ b/test/valid/unicode-literal.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "string", "value": "δ"}
+}
diff --git a/test/valid/unicode-literal.toml b/test/valid/unicode-literal.toml
new file mode 100644
index 0000000..c65723c
--- /dev/null
+++ b/test/valid/unicode-literal.toml
@@ -0,0 +1 @@
+answer = "δ"
diff --git a/toml.py b/toml.py
new file mode 100644
index 0000000..bae4b3e
--- /dev/null
+++ b/toml.py
@@ -0,0 +1,406 @@
+import string, re
+
+class TomlError(RuntimeError):
+    def __init__(self, kind, line, col):
+        self.kind = kind
+        self.line = line
+        self.col = col
+        RuntimeError.__init__(self, kind)
+
+    def __str__(self):
+        return '%s(%d, %d)' % (self.kind, self.line, self.col)
+
+class _CharSource:
+    def __init__(self, s):
+        self._s = s
+        self._index = 0
+        self._mark = 0
+        self._line = 1
+        self._col = 1
+        self._update_cur()
+
+    def __bool__(self):
+        return self.cur is not None
+
+    def __len__(self):
+        return len(self._s[self._index:])
+
+    def __getitem__(self, item):
+        return self._s[self._index:][item]
+
+    def next(self, l=1):
+        for ch in self[:l]:
+            if ch == '\n':
+                self._line += 1
+                self._col = 1
+            else:
+                self._col += 1
+        self._index += l
+        self._update_cur()
+
+    def mark(self):
+        self._mark = self._index
+        self._mark_pos = self._line, self._col
+
+    def rollback(self):
+        self._index = self._mark
+        self._line, self._col = self._mark_pos
+        self._update_cur()
+
+    def commit(self, type=None, text=None):
+        tok = self._s[self._mark:self._index]
+        pos = (self._mark_pos, (self._line, self._col))
+        if type is None:
+            type = tok
+        if text is None:
+            text = tok
+        return type, text, pos
+
+    def error(self, kind):
+        raise TomlError(kind, self._line, self._col)
+
+    def _update_cur(self):
+        self.tail = self._s[self._index:]
+        if self._index < len(self._s):
+            self.cur = self._s[self._index]
+        else:
+            self.cur = None
+
+def lex(s):
+    src = _CharSource(s.replace('\r\n', '\n'))
+    def is_id(ch):
+        return ch is not None and (ch.isalnum() or ch in '-_')
+
+    def is_ws(ch):
+        return ch is not None and ch in ' \t'
+
+    def fetch_esc():
+        escapes = {'b':'\b', 't':'\t', 'n':'\n', 'f':'\f', 'r':'\r', '"':'"', '\\':'\\'}
+        if src.cur == 'u':
+            if len(src) < 5 or any(ch not in string.hexdigits for ch in src[1:5]):
+                src.error('invalid_escape_sequence')
+            res = unichr(int(src[1:5], 16))
+            src.next(5)
+        elif src.cur == 'U':
+            if len(src) < 9 or any(ch not in string.hexdigits for ch in src[1:9]):
+                src.error('invalid_escape_sequence')
+            res = unichr(int(src[1:9], 16))
+            src.next(9)
+        elif src.cur == '\n':
+            while src and src.cur in ' \n\t':
+                src.next()
+            res = ''
+        elif src.cur in escapes:
+            res = escapes[src.cur]
+            src.next(1)
+        else:
+            src.error('invalid_escape_sequence')
+        return res
+
+    datetime_re = re.compile(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})')
+    def consume_datetime():
+        m = datetime_re.match(src.tail)
+        if not m:
+            return False
+        src.next(len(m.group(0)))
+        return True
+
+    def consume_int():
+        if not src:
+            src.error('malformed')
+        if src.cur in '+-':
+            src.next()
+        if not src or src.cur not in '0123456789':
+            src.error('malformed')
+        while src and src.cur in '0123456789_':
+            src.next()
+
+    def consume_float():
+        consume_int()
+        type = 'int'
+        if src and src.cur == '.':
+            type = 'float'
+            src.next()
+            if not src or src.cur not in '0123456789_':
+                src.error('malformed_float')
+            while src and src.cur in '0123456789_':
+                src.next()
+        if src and src.cur in 'eE':
+            type = 'float'
+            src.next()
+            consume_int()
+        return type
+
+    while src:
+        src.mark()
+        if src.cur in ' \t':
+            src.next()
+            while src and src.cur in ' \t':
+                src.next()
+        elif src.cur == '#':
+            src.next()
+            while src and src.cur != '\n':
+                src.next()
+        elif src.cur in '0123456789':
+            if consume_datetime():
+                yield src.commit('datetime')
+            else:
+                src.rollback()
+                type = consume_float()
+                yield src.commit(type)
+        elif src.cur in '+-':
+            type = consume_float()
+            yield src.commit(type)
+        elif is_id(src.cur):
+            while is_id(src.cur):
+                src.next()
+            yield src.commit('id')
+        elif src.cur in '[]{}=.,\n':
+            src.next()
+            yield src.commit()
+        elif src.tail.startswith("'''"):
+            src.next(3)
+            if src.cur == '\n':
+                src.next()
+            end_quote = src.tail.find("'''")
+            if end_quote == -1:
+                src.error('unclosed_multiline_string')
+            text = src[:end_quote]
+            src.next(end_quote+3)
+            yield src.commit('str', text)
+        elif src.cur == "'":
+            src.next()
+            end_quote = src.tail.find("'")
+            if end_quote == -1:
+                src.error('unclosed_string')
+            text = src[:end_quote]
+            src.next(end_quote+1)
+            yield src.commit('str', text)
+        elif src.tail.startswith('"""'):
+            src.next(3)
+            if src.cur == '\n':
+                src.next()
+            res = []
+            while True:
+                src.mark()
+                end_quote = src.tail.find('"""')
+                if end_quote == -1:
+                    src.error('unclosed_multiline_string')
+                esc_pos = src.tail.find('\\')
+                if esc_pos == -1 or esc_pos > end_quote:
+                    res.append(src[:end_quote])
+                    src.next(end_quote+3)
+                    break
+                res.append(src[:esc_pos])
+                src.next(esc_pos+1)
+                res.append(fetch_esc())
+
+            yield src.commit('str', ''.join(res))
+        elif src.cur == '"':
+            src.next()
+            res = []
+            while True:
+                src.mark()
+                end_quote = src.tail.find('"')
+                if end_quote == -1:
+                    src.error('unclosed_string')
+                esc_pos = src.tail.find('\\')
+                if esc_pos == -1 or esc_pos > end_quote:
+                    res.append(src[:end_quote])
+                    src.next(end_quote+1)
+                    break
+                res.append(src[:esc_pos])
+                src.next(esc_pos+1)
+                res.append(fetch_esc())
+
+            yield src.commit('str', ''.join(res))
+        else:
+            src.error('unexpected_char')
+
+    src.mark()
+    yield src.commit('\n', '')
+    yield src.commit('eof', '')
+
+class _TokSource:
+    def __init__(self, s):
+        self._lex = iter(lex(s))
+        self.pos = None
+        self.next()
+
+    def next(self):
+        self.prev_pos = self.pos
+        self.tok, self.text, self.pos = next(self._lex)
+
+    def consume(self, kind):
+        if self.tok == kind:
+            self.next()
+            return True
+        return False
+
+    def consume_adjacent(self, kind):
+        if self.prev_pos is None or self.prev_pos[1] != self.pos[0]:
+            return False
+        return self.consume(kind)
+
+    def consume_nls(self):
+        while self.consume('\n'):
+            pass
+
+    def error(self, kind):
+        raise TomlError(kind, self.pos[0][0], self.pos[0][1])
+
+def _translate_literal(type, text):
+    if type == 'bool':
+        return text == 'true'
+    elif type == 'int':
+        return int(text.replace('_', ''))
+    elif type == 'float':
+        return float(text.replace('_', ''))
+    elif type == 'str':
+        return text
+    elif type == 'datetime':
+        return text
+
+def load(fin, translate_literal=_translate_literal, translate_array=id):
+    return loads(fin.read(), translate_literal=translate_literal, translate_array=translate_array)
+
+def loads(s, translate_literal=_translate_literal, translate_array=id):
+    if isinstance(s, str):
+        s = s.decode('utf-8')
+
+    toks = _TokSource(s)
+
+    def read_value():
+        while True:
+            if toks.tok == 'id':
+                if toks.text in ('true', 'false'):
+                    value = translate_literal('bool', toks.text)
+                    toks.next()
+                    return 'bool', value
+                else:
+                    toks.error('unexpected_identifier')
+            elif toks.tok in ('int', 'str', 'float', 'datetime'):
+                type = toks.tok
+                value = translate_literal(toks.tok, toks.text)
+                toks.next()
+                return type, value
+            elif toks.consume('['):
+                res = []
+                toks.consume_nls()
+                if not toks.consume(']'):
+                    toks.consume_nls()
+                    type, val = read_value()
+                    res.append(val)
+                    toks.consume_nls()
+                    while toks.consume(','):
+                        toks.consume_nls()
+                        if toks.consume(']'):
+                            break
+                        cur_type, val = read_value()
+                        if type != cur_type:
+                            toks.error('heterogenous_array')
+                        res.append(val)
+                        toks.consume_nls()
+                    else:
+                        if not toks.consume(']'):
+                            toks.error('expected_right_brace')
+                return 'array', translate_array(res)
+            elif toks.consume('{'):
+                res = {}
+                while toks.tok in ('id', 'str'):
+                    k = toks.text
+                    toks.next()
+                    if k in res:
+                        toks.error('duplicate_key')
+                    if not toks.consume('='):
+                        toks.error('expected_equals')
+                    type, v = read_value()
+                    res[k] = v
+                    if not toks.consume(','):
+                        break
+                if not toks.consume('}'):
+                    toks.error('expected_closing_brace')
+                return 'table', res
+            else:
+                toks.error('unexpected_token')
+
+    root = {}
+    tables = {}
+    scope = root
+
+    while toks.tok != 'eof':
+        if toks.tok in ('id', 'str'):
+            k = toks.text
+            toks.next()
+            if not toks.consume('='):
+                toks.error('expected_equals')
+            type, v = read_value()
+            if k in scope:
+                toks.error('duplicate_keys')
+            scope[k] = v
+        elif toks.consume('\n'):
+            pass
+        elif toks.consume('['):
+            is_table_array = toks.consume_adjacent('[')
+
+            path = []
+            if toks.tok not in ('id', 'str'):
+                toks.error('expected_table_name')
+            path.append(toks.text)
+            toks.next()
+            while toks.consume('.'):
+                if toks.tok not in ('id', 'str'):
+                    toks.error('expected_table_name')
+                path.append(toks.text)
+                toks.next()
+            if not toks.consume(']') or (is_table_array and not toks.consume_adjacent(']')):
+                toks.error('malformed_table_name')
+            if not toks.consume('\n'):
+                toks.error('garbage_after_table_name')
+
+            cur = tables
+            for name in path[:-1]:
+                if isinstance(cur.get(name), list):
+                    d, cur = cur[name][-1]
+                else:
+                    d, cur = cur.setdefault(name, (None, {}))
+
+            scope = {}
+            name = path[-1]
+            if name not in cur:
+                if is_table_array:
+                    cur[name] = [(scope, {})]
+                else:
+                    cur[name] = (scope, {})
+            elif isinstance(cur[name], list):
+                if not is_table_array:
+                    toks.error('table_type_mismatch')
+                cur[name].append((scope, {}))
+            else:
+                if is_table_array:
+                    toks.error('table_type_mismatch')
+                old_scope, next_table = cur[name]
+                if old_scope is not None:
+                    toks.error('duplicate_tables')
+                cur[name] = (scope, next_table)
+        else:
+            toks.error('unexpected')
+
+    def merge_tables(scope, tables):
+        if scope is None:
+            scope = {}
+        for k, v in tables.iteritems():
+            if k in scope:
+                toks.error('key_table_conflict')
+            if isinstance(v, list):
+                scope[k] = [merge_tables(sc, tbl) for sc, tbl in v]
+            else:
+                scope[k] = merge_tables(v[0], v[1])
+        return scope
+
+    return merge_tables(root, tables)
+
+if __name__ == '__main__':
+    import sys, json
+    t = sys.stdin.read()
+    print json.dumps(loads(t), indent=4)