From 11115f13a3499420cd09b745a298ef071755b24b Mon Sep 17 00:00:00 2001
From: Alex Crichton <alex@alexcrichton.com>
Date: Fri, 20 Jun 2014 17:01:38 -0700
Subject: Initial commit

---
 src/parser.rs                                      | 617 +++++++++++++++++++++
 src/test/README.md                                 |   1 +
 .../array-mixed-types-ints-and-floats.json         |  15 +
 src/test/invalid.rs                                |  76 +++
 .../invalid/array-mixed-types-arrays-and-ints.toml |   1 +
 .../invalid/array-mixed-types-ints-and-floats.toml |   1 +
 .../array-mixed-types-strings-and-ints.toml        |   1 +
 src/test/invalid/datetime-malformed-no-leads.toml  |   1 +
 src/test/invalid/datetime-malformed-no-secs.toml   |   1 +
 src/test/invalid/datetime-malformed-no-t.toml      |   1 +
 src/test/invalid/datetime-malformed-no-z.toml      |   1 +
 .../invalid/datetime-malformed-with-milli.toml     |   1 +
 src/test/invalid/duplicate-key-table.toml          |   5 +
 src/test/invalid/duplicate-keys.toml               |   2 +
 src/test/invalid/duplicate-tables.toml             |   2 +
 src/test/invalid/empty-implicit-table.toml         |   1 +
 src/test/invalid/empty-table.toml                  |   1 +
 src/test/invalid/float-no-leading-zero.toml        |   2 +
 src/test/invalid/float-no-trailing-digits.toml     |   2 +
 src/test/invalid/key-two-equals.toml               |   1 +
 src/test/invalid/string-bad-byte-escape.toml       |   1 +
 src/test/invalid/string-bad-escape.toml            |   1 +
 src/test/invalid/string-byte-escapes.toml          |   1 +
 src/test/invalid/string-no-close.toml              |   1 +
 src/test/invalid/table-array-implicit.toml         |  14 +
 .../invalid/table-array-malformed-bracket.toml     |   2 +
 src/test/invalid/table-array-malformed-empty.toml  |   2 +
 src/test/invalid/table-nested-brackets-close.toml  |   2 +
 src/test/invalid/table-nested-brackets-open.toml   |   2 +
 src/test/invalid/text-after-array-entries.toml     |   4 +
 src/test/invalid/text-after-integer.toml           |   1 +
 src/test/invalid/text-after-string.toml            |   1 +
 src/test/invalid/text-after-table.toml             |   1 +
 src/test/invalid/text-before-array-separator.toml  |   4 +
 src/test/invalid/text-in-array.toml                |   5 +
 src/test/mod.rs                                    |   2 +
 src/test/valid.rs                                  | 163 ++++++
 src/test/valid/array-empty.json                    |  11 +
 src/test/valid/array-empty.toml                    |   1 +
 src/test/valid/array-nospaces.json                 |  10 +
 src/test/valid/array-nospaces.toml                 |   1 +
 src/test/valid/arrays-hetergeneous.json            |  19 +
 src/test/valid/arrays-hetergeneous.toml            |   1 +
 src/test/valid/arrays-nested.json                  |  13 +
 src/test/valid/arrays-nested.toml                  |   1 +
 src/test/valid/arrays.json                         |  34 ++
 src/test/valid/arrays.toml                         |   9 +
 src/test/valid/bool.json                           |   4 +
 src/test/valid/bool.toml                           |   2 +
 src/test/valid/comments-everywhere.json            |  12 +
 src/test/valid/comments-everywhere.toml            |  24 +
 src/test/valid/datetime.json                       |   3 +
 src/test/valid/datetime.toml                       |   1 +
 src/test/valid/empty.json                          |   1 +
 src/test/valid/empty.toml                          |   0
 src/test/valid/example.json                        |  14 +
 src/test/valid/example.toml                        |   5 +
 src/test/valid/float.json                          |   4 +
 src/test/valid/float.toml                          |   2 +
 src/test/valid/implicit-and-explicit-after.json    |  10 +
 src/test/valid/implicit-and-explicit-after.toml    |   5 +
 src/test/valid/implicit-and-explicit-before.json   |  10 +
 src/test/valid/implicit-and-explicit-before.toml   |   5 +
 src/test/valid/implicit-groups.json                |   9 +
 src/test/valid/implicit-groups.toml                |   2 +
 src/test/valid/integer.json                        |   4 +
 src/test/valid/integer.toml                        |   2 +
 src/test/valid/key-equals-nospace.json             |   3 +
 src/test/valid/key-equals-nospace.toml             |   1 +
 src/test/valid/key-special-chars.json              |   5 +
 src/test/valid/key-special-chars.toml              |   1 +
 src/test/valid/key-with-pound.json                 |   3 +
 src/test/valid/key-with-pound.toml                 |   1 +
 src/test/valid/long-float.json                     |   4 +
 src/test/valid/long-float.toml                     |   2 +
 src/test/valid/long-integer.json                   |   4 +
 src/test/valid/long-integer.toml                   |   2 +
 src/test/valid/string-escapes.json                 |  34 ++
 src/test/valid/string-escapes.toml                 |   8 +
 src/test/valid/string-simple.json                  |   6 +
 src/test/valid/string-simple.toml                  |   1 +
 src/test/valid/string-with-pound.json              |   7 +
 src/test/valid/string-with-pound.toml              |   2 +
 src/test/valid/table-array-implicit.json           |   7 +
 src/test/valid/table-array-implicit.toml           |   2 +
 src/test/valid/table-array-many.json               |  16 +
 src/test/valid/table-array-many.toml               |  11 +
 src/test/valid/table-array-nest.json               |  18 +
 src/test/valid/table-array-nest.toml               |  17 +
 src/test/valid/table-array-one.json                |   8 +
 src/test/valid/table-array-one.toml                |   3 +
 src/test/valid/table-empty.json                    |   3 +
 src/test/valid/table-empty.toml                    |   1 +
 src/test/valid/table-sub-empty.json                |   3 +
 src/test/valid/table-sub-empty.toml                |   2 +
 src/test/valid/table-whitespace.json               |   3 +
 src/test/valid/table-whitespace.toml               |   1 +
 src/test/valid/table-with-pound.json               |   5 +
 src/test/valid/table-with-pound.toml               |   2 +
 src/test/valid/unicode-escape.json                 |   3 +
 src/test/valid/unicode-escape.toml                 |   1 +
 src/test/valid/unicode-literal.json                |   3 +
 src/test/valid/unicode-literal.toml                |   1 +
 src/toml.rs                                        |  51 ++
 104 files changed, 1404 insertions(+)
 create mode 100644 src/parser.rs
 create mode 100644 src/test/README.md
 create mode 100644 src/test/invalid-encoder/array-mixed-types-ints-and-floats.json
 create mode 100644 src/test/invalid.rs
 create mode 100644 src/test/invalid/array-mixed-types-arrays-and-ints.toml
 create mode 100644 src/test/invalid/array-mixed-types-ints-and-floats.toml
 create mode 100644 src/test/invalid/array-mixed-types-strings-and-ints.toml
 create mode 100644 src/test/invalid/datetime-malformed-no-leads.toml
 create mode 100644 src/test/invalid/datetime-malformed-no-secs.toml
 create mode 100644 src/test/invalid/datetime-malformed-no-t.toml
 create mode 100644 src/test/invalid/datetime-malformed-no-z.toml
 create mode 100644 src/test/invalid/datetime-malformed-with-milli.toml
 create mode 100644 src/test/invalid/duplicate-key-table.toml
 create mode 100644 src/test/invalid/duplicate-keys.toml
 create mode 100644 src/test/invalid/duplicate-tables.toml
 create mode 100644 src/test/invalid/empty-implicit-table.toml
 create mode 100644 src/test/invalid/empty-table.toml
 create mode 100644 src/test/invalid/float-no-leading-zero.toml
 create mode 100644 src/test/invalid/float-no-trailing-digits.toml
 create mode 100644 src/test/invalid/key-two-equals.toml
 create mode 100644 src/test/invalid/string-bad-byte-escape.toml
 create mode 100644 src/test/invalid/string-bad-escape.toml
 create mode 100644 src/test/invalid/string-byte-escapes.toml
 create mode 100644 src/test/invalid/string-no-close.toml
 create mode 100644 src/test/invalid/table-array-implicit.toml
 create mode 100644 src/test/invalid/table-array-malformed-bracket.toml
 create mode 100644 src/test/invalid/table-array-malformed-empty.toml
 create mode 100644 src/test/invalid/table-nested-brackets-close.toml
 create mode 100644 src/test/invalid/table-nested-brackets-open.toml
 create mode 100644 src/test/invalid/text-after-array-entries.toml
 create mode 100644 src/test/invalid/text-after-integer.toml
 create mode 100644 src/test/invalid/text-after-string.toml
 create mode 100644 src/test/invalid/text-after-table.toml
 create mode 100644 src/test/invalid/text-before-array-separator.toml
 create mode 100644 src/test/invalid/text-in-array.toml
 create mode 100644 src/test/mod.rs
 create mode 100644 src/test/valid.rs
 create mode 100644 src/test/valid/array-empty.json
 create mode 100644 src/test/valid/array-empty.toml
 create mode 100644 src/test/valid/array-nospaces.json
 create mode 100644 src/test/valid/array-nospaces.toml
 create mode 100644 src/test/valid/arrays-hetergeneous.json
 create mode 100644 src/test/valid/arrays-hetergeneous.toml
 create mode 100644 src/test/valid/arrays-nested.json
 create mode 100644 src/test/valid/arrays-nested.toml
 create mode 100644 src/test/valid/arrays.json
 create mode 100644 src/test/valid/arrays.toml
 create mode 100644 src/test/valid/bool.json
 create mode 100644 src/test/valid/bool.toml
 create mode 100644 src/test/valid/comments-everywhere.json
 create mode 100644 src/test/valid/comments-everywhere.toml
 create mode 100644 src/test/valid/datetime.json
 create mode 100644 src/test/valid/datetime.toml
 create mode 100644 src/test/valid/empty.json
 create mode 100644 src/test/valid/empty.toml
 create mode 100644 src/test/valid/example.json
 create mode 100644 src/test/valid/example.toml
 create mode 100644 src/test/valid/float.json
 create mode 100644 src/test/valid/float.toml
 create mode 100644 src/test/valid/implicit-and-explicit-after.json
 create mode 100644 src/test/valid/implicit-and-explicit-after.toml
 create mode 100644 src/test/valid/implicit-and-explicit-before.json
 create mode 100644 src/test/valid/implicit-and-explicit-before.toml
 create mode 100644 src/test/valid/implicit-groups.json
 create mode 100644 src/test/valid/implicit-groups.toml
 create mode 100644 src/test/valid/integer.json
 create mode 100644 src/test/valid/integer.toml
 create mode 100644 src/test/valid/key-equals-nospace.json
 create mode 100644 src/test/valid/key-equals-nospace.toml
 create mode 100644 src/test/valid/key-special-chars.json
 create mode 100644 src/test/valid/key-special-chars.toml
 create mode 100644 src/test/valid/key-with-pound.json
 create mode 100644 src/test/valid/key-with-pound.toml
 create mode 100644 src/test/valid/long-float.json
 create mode 100644 src/test/valid/long-float.toml
 create mode 100644 src/test/valid/long-integer.json
 create mode 100644 src/test/valid/long-integer.toml
 create mode 100644 src/test/valid/string-escapes.json
 create mode 100644 src/test/valid/string-escapes.toml
 create mode 100644 src/test/valid/string-simple.json
 create mode 100644 src/test/valid/string-simple.toml
 create mode 100644 src/test/valid/string-with-pound.json
 create mode 100644 src/test/valid/string-with-pound.toml
 create mode 100644 src/test/valid/table-array-implicit.json
 create mode 100644 src/test/valid/table-array-implicit.toml
 create mode 100644 src/test/valid/table-array-many.json
 create mode 100644 src/test/valid/table-array-many.toml
 create mode 100644 src/test/valid/table-array-nest.json
 create mode 100644 src/test/valid/table-array-nest.toml
 create mode 100644 src/test/valid/table-array-one.json
 create mode 100644 src/test/valid/table-array-one.toml
 create mode 100644 src/test/valid/table-empty.json
 create mode 100644 src/test/valid/table-empty.toml
 create mode 100644 src/test/valid/table-sub-empty.json
 create mode 100644 src/test/valid/table-sub-empty.toml
 create mode 100644 src/test/valid/table-whitespace.json
 create mode 100644 src/test/valid/table-whitespace.toml
 create mode 100644 src/test/valid/table-with-pound.json
 create mode 100644 src/test/valid/table-with-pound.toml
 create mode 100644 src/test/valid/unicode-escape.json
 create mode 100644 src/test/valid/unicode-escape.toml
 create mode 100644 src/test/valid/unicode-literal.json
 create mode 100644 src/test/valid/unicode-literal.toml
 create mode 100644 src/toml.rs

diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..dac3e28
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,617 @@
+use std::char;
+use std::collections::{HashMap, HashSet};
+use std::num::FromStrRadix;
+use std::str;
+
+use {Array, Table, Value, String, Float, Integer, Boolean, Datetime};
+
+pub struct Parser<'a> {
+    input: &'a str,
+    cur: str::CharOffsets<'a>,
+    tables_defined: HashSet<String>,
+    pub errors: Vec<Error>,
+}
+
+#[deriving(Show)]
+pub struct Error {
+    pub lo: uint,
+    pub hi: uint,
+    pub desc: String,
+}
+
+impl<'a> Parser<'a> {
+    pub fn new(s: &'a str) -> Parser<'a> {
+        Parser {
+            input: s,
+            cur: s.char_indices(),
+            errors: Vec::new(),
+            tables_defined: HashSet::new(),
+        }
+    }
+
+    fn next_pos(&self) -> uint {
+        self.cur.clone().next().map(|p| p.val0()).unwrap_or(self.input.len())
+    }
+
+    fn eat(&mut self, ch: char) -> bool {
+        match self.cur.clone().next() {
+            Some((_, c)) if c == ch => { self.cur.next(); true }
+            Some(_) | None => false,
+        }
+    }
+
+    fn expect(&mut self, ch: char) -> bool {
+        if self.eat(ch) { return true }
+        let mut it = self.cur.clone();
+        let lo = it.next().map(|p| p.val0()).unwrap_or(self.input.len());
+        let hi = it.next().map(|p| p.val0()).unwrap_or(self.input.len());
+        self.errors.push(Error {
+            lo: lo,
+            hi: hi,
+            desc: match self.cur.clone().next() {
+                Some((_, c)) => format!("expected `{}`, but found `{}`", ch, c),
+                None => format!("expected `{}`, but found eof", ch)
+            }
+        });
+        false
+    }
+
+    fn ws(&mut self) {
+        loop {
+            match self.cur.clone().next() {
+                Some((_, '\t')) |
+                Some((_, ' ')) => { self.cur.next(); }
+                _ => break,
+            }
+        }
+    }
+
+    fn comment(&mut self) {
+        match self.cur.clone().next() {
+            Some((_, '#')) => {}
+            _ => return,
+        }
+        for (_, ch) in self.cur {
+            if ch == '\n' { break }
+        }
+    }
+
+    pub fn parse(&mut self) -> Option<Table> {
+        let mut ret = HashMap::new();
+        loop {
+            self.ws();
+            match self.cur.clone().next() {
+                Some((_, '#')) => { self.comment(); }
+                Some((_, '\n')) => { self.cur.next(); }
+                Some((start, '[')) => {
+                    self.cur.next();
+                    let array = self.eat('[');
+                    let mut section = String::new();
+                    for (pos, ch) in self.cur {
+                        if ch == ']' { break }
+                        if ch == '[' {
+                            self.errors.push(Error {
+                                lo: pos,
+                                hi: pos + 1,
+                                desc: format!("section names cannot contain \
+                                               a `[` character"),
+                            });
+                            continue
+                        }
+                        section.push_char(ch);
+                    }
+
+                    if section.len() == 0 {
+                        self.errors.push(Error {
+                            lo: start,
+                            hi: start + if array {3} else {1},
+                            desc: format!("section name must not be empty"),
+                        });
+                        continue
+                    } else if array && !self.expect(']') {
+                        return None
+                    }
+
+                    let mut table = HashMap::new();
+                    if !self.values(&mut table) { return None }
+                    if array {
+                        self.insert_array(&mut ret, section, Table(table), start)
+                    } else {
+                        self.insert_table(&mut ret, section, table, start)
+                    }
+                }
+                Some(_) => {
+                    if !self.values(&mut ret) { return None }
+                }
+                None if self.errors.len() == 0 => return Some(ret),
+                None => return None,
+            }
+        }
+    }
+
+    fn values(&mut self, into: &mut Table) -> bool {
+        loop {
+            self.ws();
+            match self.cur.clone().next() {
+                Some((_, '#')) => self.comment(),
+                Some((_, '\n')) => { self.cur.next(); }
+                Some((_, '[')) => break,
+                Some((start, _)) => {
+                    let mut key = String::new();
+                    let mut found_eq = false;
+                    for (pos, ch) in self.cur {
+                        match ch {
+                            ' ' | '\t' => break,
+                            '=' => { found_eq = true; break }
+                            '\n' => {
+                                self.errors.push(Error {
+                                    lo: start,
+                                    hi: pos + 1,
+                                    desc: format!("keys cannot be defined \
+                                                   across lines"),
+                                })
+                            }
+                            c => key.push_char(c),
+                        }
+                    }
+                    if !found_eq {
+                        self.ws();
+                        if !self.expect('=') { return false }
+                    }
+
+                    let value = match self.value() {
+                        Some(value) => value,
+                        None => return false,
+                    };
+                    self.insert(into, key, value, start);
+                    self.ws();
+                    self.comment();
+                    self.eat('\n');
+                }
+                None => break,
+            }
+        }
+        return true
+    }
+
+    fn value(&mut self) -> Option<Value> {
+        self.ws();
+        match self.cur.clone().next() {
+            Some((pos, '"')) => self.string(pos),
+            Some((pos, 't')) |
+            Some((pos, 'f')) => self.boolean(pos),
+            Some((pos, '[')) => self.array(pos),
+            Some((pos, '-')) => self.number_or_datetime(pos),
+            Some((pos, ch)) if ch.is_digit() => self.number_or_datetime(pos),
+            _ => {
+                let mut it = self.cur.clone();
+                let lo = it.next().map(|p| p.val0()).unwrap_or(self.input.len());
+                let hi = it.next().map(|p| p.val0()).unwrap_or(self.input.len());
+                self.errors.push(Error {
+                    lo: lo,
+                    hi: hi,
+                    desc: format!("expected a value"),
+                });
+                return None
+            }
+        }
+    }
+
+    fn string(&mut self, start: uint) -> Option<Value> {
+        if !self.expect('"') { return None }
+        let mut ret = String::new();
+
+        loop {
+            match self.cur.next() {
+                Some((_, '"')) => break,
+                Some((pos, '\\')) => {
+                    match escape(self, pos) {
+                        Some(c) => ret.push_char(c),
+                        None => {}
+                    }
+                }
+                Some((pos, ch)) if ch < '\u001f' => {
+                    let mut escaped = String::new();
+                    ch.escape_default(|c| escaped.push_char(c));
+                    self.errors.push(Error {
+                        lo: pos,
+                        hi: pos + 1,
+                        desc: format!("control character `{}` must be escaped",
+                                      escaped)
+                    });
+                }
+                Some((_, ch)) => ret.push_char(ch),
+                None => {
+                    self.errors.push(Error {
+                        lo: start,
+                        hi: self.input.len(),
+                        desc: format!("unterminated string literal"),
+                    });
+                    return None
+                }
+            }
+        }
+
+        return Some(String(ret));
+
+        fn escape(me: &mut Parser, pos: uint) -> Option<char> {
+            match me.cur.next() {
+                Some((_, 'b')) => Some('\u0008'),
+                Some((_, 't')) => Some('\u0009'),
+                Some((_, 'n')) => Some('\u000a'),
+                Some((_, 'f')) => Some('\u000c'),
+                Some((_, 'r')) => Some('\u000d'),
+                Some((_, '"')) => Some('\u0022'),
+                Some((_, '/')) => Some('\u002f'),
+                Some((_, '\\')) => Some('\u005c'),
+                Some((pos, 'u')) => {
+                    let num = if me.input.is_char_boundary(pos + 5) {
+                        me.input.slice(pos + 1, pos + 5)
+                    } else {
+                        "invalid"
+                    };
+                    match FromStrRadix::from_str_radix(num, 16) {
+                        Some(n) => {
+                            match char::from_u32(n) {
+                                Some(c) => {
+                                    me.cur.next();
+                                    me.cur.next();
+                                    me.cur.next();
+                                    me.cur.next();
+                                    return Some(c)
+                                }
+                                None => {
+                                    me.errors.push(Error {
+                                        lo: pos + 1,
+                                        hi: pos + 5,
+                                        desc: format!("codepoint `{:x}` is \
+                                                       not a valid unicode \
+                                                       codepoint", n),
+                                    })
+                                }
+                            }
+                        }
+                        None => {
+                            me.errors.push(Error {
+                                lo: pos,
+                                hi: pos + 1,
+                                desc: format!("expected four hex digits \
+                                               after a `u` escape"),
+                            })
+                        }
+                    }
+                    None
+                }
+                Some((pos, ch)) => {
+                    let mut escaped = String::new();
+                    ch.escape_default(|c| escaped.push_char(c));
+                    let next_pos = me.next_pos();
+                    me.errors.push(Error {
+                        lo: pos,
+                        hi: next_pos,
+                        desc: format!("unknown string escape: `{}`",
+                                      escaped),
+                    });
+                    None
+                }
+                None => {
+                    me.errors.push(Error {
+                        lo: pos,
+                        hi: pos + 1,
+                        desc: format!("unterminated escape sequence"),
+                    });
+                    None
+                }
+            }
+        }
+    }
+
+    fn number_or_datetime(&mut self, start: uint) -> Option<Value> {
+        let negative = self.eat('-');
+        let mut is_float = false;
+        loop {
+            match self.cur.clone().next() {
+                Some((_, ch)) if ch.is_digit() => { self.cur.next(); }
+                Some((_, '.')) if !is_float => {
+                    is_float = true;
+                    self.cur.next();
+                }
+                Some(_) | None => break,
+            }
+        }
+        let end = self.next_pos();
+        let ret = if is_float {
+            if self.input.char_at_reverse(end) == '.' {
+                None
+            } else {
+                from_str::<f64>(self.input.slice(start, end)).map(Float)
+            }
+        } else if !negative && self.eat('-') {
+            self.datetime(start, end + 1)
+        } else {
+            from_str::<i64>(self.input.slice(start, end)).map(Integer)
+        };
+        if ret.is_none() {
+            self.errors.push(Error {
+                lo: start,
+                hi: end,
+                desc: format!("invalid numeric literal"),
+            });
+        }
+        return ret;
+    }
+
+    fn boolean(&mut self, start: uint) -> Option<Value> {
+        let rest = self.input.slice_from(start);
+        if rest.starts_with("true") {
+            for _ in range(0, 4) {
+                self.cur.next();
+            }
+            Some(Boolean(true))
+        } else if rest.starts_with("false") {
+            for _ in range(0, 5) {
+                self.cur.next();
+            }
+            Some(Boolean(false))
+        } else {
+            let next = self.next_pos();
+            self.errors.push(Error {
+                lo: start,
+                hi: next,
+                desc: format!("unexpected character: `{}`",
+                             rest.char_at(0)),
+            });
+            None
+        }
+    }
+
+    fn datetime(&mut self, start: uint, end_so_far: uint) -> Option<Value> {
+        let mut date = self.input.slice(start, end_so_far).to_string();
+        for _ in range(0, 15) {
+            match self.cur.next() {
+                Some((_, ch)) => date.push_char(ch),
+                None => {
+                    self.errors.push(Error {
+                        lo: start,
+                        hi: end_so_far,
+                        desc: format!("malformed date literal"),
+                    });
+                    return None
+                }
+            }
+        }
+        let mut it = date.as_slice().chars();
+        let mut valid = true;
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c == '-').unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c == '-').unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c == 'T').unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c == ':').unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c == ':').unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c.is_digit()).unwrap_or(false);
+        valid = valid && it.next().map(|c| c == 'Z').unwrap_or(false);
+        if valid {
+            Some(Datetime(date.clone()))
+        } else {
+            self.errors.push(Error {
+                lo: start,
+                hi: start + date.len(),
+                desc: format!("malformed date literal"),
+            });
+            None
+        }
+    }
+
+    fn array(&mut self, _start: uint) -> Option<Value> {
+        if !self.expect('[') { return None }
+        let mut ret = Vec::new();
+        fn consume(me: &mut Parser) {
+            loop {
+                me.ws();
+                match me.cur.clone().next() {
+                    Some((_, '#')) => { me.comment(); }
+                    Some((_, '\n')) => { me.cur.next(); }
+                    _ => break,
+                }
+            }
+        }
+        let mut type_str = None;
+        loop {
+            // Break out early if we see the closing bracket
+            consume(self);
+            if self.eat(']') { return Some(Array(ret)) }
+
+            // Attempt to parse a value, triggering an error if it's the wrong
+            // type.
+            let start = self.next_pos();
+            let value = match self.value() {
+                Some(v) => v,
+                None => return None,
+            };
+            let end = self.next_pos();
+            let expected = type_str.unwrap_or(value.type_str());
+            if value.type_str() != expected {
+                self.errors.push(Error {
+                    lo: start,
+                    hi: end,
+                    desc: format!("expected type `{}`, found type `{}`",
+                                  expected, value.type_str()),
+                });
+            } else {
+                type_str = Some(expected);
+                ret.push(value);
+            }
+
+            // Look for a comma. If we don't find one we're done
+            consume(self);
+            if !self.eat(',') { break }
+        }
+        consume(self);
+        if !self.expect(']') { return None }
+        return Some(Array(ret))
+    }
+
+    fn insert(&mut self, into: &mut Table, key: String, value: Value,
+              key_lo: uint) {
+        if into.contains_key(&key) {
+            self.errors.push(Error {
+                lo: key_lo,
+                hi: key_lo + key.len(),
+                desc: format!("duplicate key: `{}`", key),
+            })
+        } else {
+            into.insert(key, value);
+        }
+    }
+
+    fn recurse<'a>(&mut self, mut cur: &'a mut Table, orig_key: &'a str,
+                   key_lo: uint) -> Option<(&'a mut Table, &'a str)> {
+        if orig_key.starts_with(".") || orig_key.ends_with(".") ||
+           orig_key.contains("..") {
+            self.errors.push(Error {
+                lo: key_lo,
+                hi: key_lo + orig_key.len(),
+                desc: format!("tables cannot have empty names"),
+            });
+            return None
+        }
+        let key = match orig_key.rfind('.') {
+            Some(n) => orig_key.slice_to(n),
+            None => return Some((cur, orig_key)),
+        };
+        for part in key.as_slice().split('.') {
+            let part = part.to_string();
+            let tmp = cur;
+
+            if tmp.contains_key(&part) {
+                match *tmp.get_mut(&part) {
+                    Table(ref mut table) => {
+                        cur = table;
+                        continue
+                    }
+                    Array(ref mut array) => {
+                        match array.as_mut_slice().mut_last() {
+                            Some(&Table(ref mut table)) => cur = table,
+                            _ => {
+                                self.errors.push(Error {
+                                    lo: key_lo,
+                                    hi: key_lo + key.len(),
+                                    desc: format!("array `{}` does not contain \
+                                                   tables", part)
+                                });
+                                return None
+                            }
+                        }
+                        continue
+                    }
+                    _ => {
+                        self.errors.push(Error {
+                            lo: key_lo,
+                            hi: key_lo + key.len(),
+                            desc: format!("key `{}` was not previously a table",
+                                          part)
+                        });
+                        return None
+                    }
+                }
+            }
+
+            // Initialize an empty table as part of this sub-key
+            tmp.insert(part.clone(), Table(HashMap::new()));
+            match *tmp.get_mut(&part) {
+                Table(ref mut inner) => cur = inner,
+                _ => unreachable!(),
+            }
+        }
+        return Some((cur, orig_key.slice_from(key.len() + 1)))
+    }
+
+    fn insert_table(&mut self, into: &mut Table, key: String, value: Table,
+                    key_lo: uint) {
+        if !self.tables_defined.insert(key.clone()) {
+            self.errors.push(Error {
+                lo: key_lo,
+                hi: key_lo + key.len(),
+                desc: format!("redefinition of table `{}`", key),
+            });
+            return
+        }
+
+        let (into, key) = match self.recurse(into, key.as_slice(), key_lo) {
+            Some(pair) => pair,
+            None => return,
+        };
+        let key = key.to_string();
+        if !into.contains_key(&key) {
+            into.insert(key.clone(), Table(HashMap::new()));
+        }
+        match into.find_mut(&key) {
+            Some(&Table(ref mut table)) => {
+                for (k, v) in value.move_iter() {
+                    if !table.insert(k.clone(), v) {
+                        self.errors.push(Error {
+                            lo: key_lo,
+                            hi: key_lo + key.len(),
+                            desc: format!("duplicate key `{}` in table", k),
+                        });
+                    }
+                }
+            }
+            Some(_) => {
+                self.errors.push(Error {
+                    lo: key_lo,
+                    hi: key_lo + key.len(),
+                    desc: format!("duplicate key `{}` in table", key),
+                });
+            }
+            None => {}
+        }
+    }
+
+    fn insert_array(&mut self, into: &mut Table, key: String, value: Value,
+                   key_lo: uint) {
+        let (into, key) = match self.recurse(into, key.as_slice(), key_lo) {
+            Some(pair) => pair,
+            None => return,
+        };
+        let key = key.to_string();
+        if !into.contains_key(&key) {
+            into.insert(key.clone(), Array(Vec::new()));
+        }
+        match *into.get_mut(&key) {
+            Array(ref mut vec) => {
+                match vec.as_slice().head() {
+                    Some(ref v) if !v.same_type(&value) => {
+                        self.errors.push(Error {
+                            lo: key_lo,
+                            hi: key_lo + key.len(),
+                            desc: format!("expected type `{}`, found type `{}`",
+                                          v.type_str(), value.type_str()),
+                        })
+                    }
+                    Some(..) | None => {}
+                }
+                vec.push(value);
+            }
+            _ => {
+                self.errors.push(Error {
+                    lo: key_lo,
+                    hi: key_lo + key.len(),
+                    desc: format!("key `{}` was previously not an array", key),
+                });
+            }
+        }
+    }
+}
diff --git a/src/test/README.md b/src/test/README.md
new file mode 100644
index 0000000..ebbc01c
--- /dev/null
+++ b/src/test/README.md
@@ -0,0 +1 @@
+Tests are from https://github.com/BurntSushi/toml-test
diff --git a/src/test/invalid-encoder/array-mixed-types-ints-and-floats.json b/src/test/invalid-encoder/array-mixed-types-ints-and-floats.json
new file mode 100644
index 0000000..b7920a0
--- /dev/null
+++ b/src/test/invalid-encoder/array-mixed-types-ints-and-floats.json
@@ -0,0 +1,15 @@
+{
+    "ints-and-floats": {
+        "type": "array",
+        "value": [
+            {
+                "type": "integer",
+                "value": "1"
+            },
+            {
+                "type": "float",
+                "value": "1.0"
+            }
+        ]
+    }
+}
diff --git a/src/test/invalid.rs b/src/test/invalid.rs
new file mode 100644
index 0000000..9f86d13
--- /dev/null
+++ b/src/test/invalid.rs
@@ -0,0 +1,76 @@
+use {Parser};
+
+fn run(toml: &str) {
+    let mut p = Parser::new(toml);
+    let table = p.parse();
+    assert!(p.errors.len() > 0);
+    assert!(table.is_none());
+}
+
+macro_rules! test( ($name:ident, $toml:expr) => (
+    #[test]
+    fn $name() { run($toml); }
+) )
+
+test!(array_mixed_types_arrays_and_ints,
+      include_str!("invalid/array-mixed-types-arrays-and-ints.toml"))
+test!(array_mixed_types_ints_and_floats,
+      include_str!("invalid/array-mixed-types-ints-and-floats.toml"))
+test!(array_mixed_types_strings_and_ints,
+      include_str!("invalid/array-mixed-types-strings-and-ints.toml"))
+test!(datetime_malformed_no_leads,
+      include_str!("invalid/datetime-malformed-no-leads.toml"))
+test!(datetime_malformed_no_secs,
+      include_str!("invalid/datetime-malformed-no-secs.toml"))
+test!(datetime_malformed_no_t,
+      include_str!("invalid/datetime-malformed-no-t.toml"))
+test!(datetime_malformed_no_z,
+      include_str!("invalid/datetime-malformed-no-z.toml"))
+test!(datetime_malformed_with_milli,
+      include_str!("invalid/datetime-malformed-with-milli.toml"))
+test!(duplicate_keys,
+      include_str!("invalid/duplicate-keys.toml"))
+test!(duplicate_key_table,
+      include_str!("invalid/duplicate-key-table.toml"))
+test!(duplicate_tables,
+      include_str!("invalid/duplicate-tables.toml"))
+test!(empty_implicit_table,
+      include_str!("invalid/empty-implicit-table.toml"))
+test!(empty_table,
+      include_str!("invalid/empty-table.toml"))
+test!(float_no_leading_zero,
+      include_str!("invalid/float-no-leading-zero.toml"))
+test!(float_no_trailing_digits,
+      include_str!("invalid/float-no-trailing-digits.toml"))
+test!(key_two_equals,
+      include_str!("invalid/key-two-equals.toml"))
+test!(string_bad_byte_escape,
+      include_str!("invalid/string-bad-byte-escape.toml"))
+test!(string_bad_escape,
+      include_str!("invalid/string-bad-escape.toml"))
+test!(string_byte_escapes,
+      include_str!("invalid/string-byte-escapes.toml"))
+test!(string_no_close,
+      include_str!("invalid/string-no-close.toml"))
+test!(table_array_implicit,
+      include_str!("invalid/table-array-implicit.toml"))
+test!(table_array_malformed_bracket,
+      include_str!("invalid/table-array-malformed-bracket.toml"))
+test!(table_array_malformed_empty,
+      include_str!("invalid/table-array-malformed-empty.toml"))
+test!(table_nested_brackets_close,
+      include_str!("invalid/table-nested-brackets-close.toml"))
+test!(table_nested_brackets_open,
+      include_str!("invalid/table-nested-brackets-open.toml"))
+test!(text_after_array_entries,
+      include_str!("invalid/text-after-array-entries.toml"))
+test!(text_after_integer,
+      include_str!("invalid/text-after-integer.toml"))
+test!(text_after_string,
+      include_str!("invalid/text-after-string.toml"))
+test!(text_after_table,
+      include_str!("invalid/text-after-table.toml"))
+test!(text_before_array_separator,
+      include_str!("invalid/text-before-array-separator.toml"))
+test!(text_in_array,
+      include_str!("invalid/text-in-array.toml"))
diff --git a/src/test/invalid/array-mixed-types-arrays-and-ints.toml b/src/test/invalid/array-mixed-types-arrays-and-ints.toml
new file mode 100644
index 0000000..051ec73
--- /dev/null
+++ b/src/test/invalid/array-mixed-types-arrays-and-ints.toml
@@ -0,0 +1 @@
+arrays-and-ints =  [1, ["Arrays are not integers."]]
diff --git a/src/test/invalid/array-mixed-types-ints-and-floats.toml b/src/test/invalid/array-mixed-types-ints-and-floats.toml
new file mode 100644
index 0000000..51ebe80
--- /dev/null
+++ b/src/test/invalid/array-mixed-types-ints-and-floats.toml
@@ -0,0 +1 @@
+ints-and-floats = [1, 1.0]
diff --git a/src/test/invalid/array-mixed-types-strings-and-ints.toml b/src/test/invalid/array-mixed-types-strings-and-ints.toml
new file mode 100644
index 0000000..f348308
--- /dev/null
+++ b/src/test/invalid/array-mixed-types-strings-and-ints.toml
@@ -0,0 +1 @@
+strings-and-ints = ["hi", 42]
diff --git a/src/test/invalid/datetime-malformed-no-leads.toml b/src/test/invalid/datetime-malformed-no-leads.toml
new file mode 100644
index 0000000..123f173
--- /dev/null
+++ b/src/test/invalid/datetime-malformed-no-leads.toml
@@ -0,0 +1 @@
+no-leads = 1987-7-05T17:45:00Z
diff --git a/src/test/invalid/datetime-malformed-no-secs.toml b/src/test/invalid/datetime-malformed-no-secs.toml
new file mode 100644
index 0000000..ba93900
--- /dev/null
+++ b/src/test/invalid/datetime-malformed-no-secs.toml
@@ -0,0 +1 @@
+no-secs = 1987-07-05T17:45Z
diff --git a/src/test/invalid/datetime-malformed-no-t.toml b/src/test/invalid/datetime-malformed-no-t.toml
new file mode 100644
index 0000000..617e3c5
--- /dev/null
+++ b/src/test/invalid/datetime-malformed-no-t.toml
@@ -0,0 +1 @@
+no-t = 1987-07-0517:45:00Z
diff --git a/src/test/invalid/datetime-malformed-no-z.toml b/src/test/invalid/datetime-malformed-no-z.toml
new file mode 100644
index 0000000..cf66b1e
--- /dev/null
+++ b/src/test/invalid/datetime-malformed-no-z.toml
@@ -0,0 +1 @@
+no-z = 1987-07-05T17:45:00
diff --git a/src/test/invalid/datetime-malformed-with-milli.toml b/src/test/invalid/datetime-malformed-with-milli.toml
new file mode 100644
index 0000000..eef792f
--- /dev/null
+++ b/src/test/invalid/datetime-malformed-with-milli.toml
@@ -0,0 +1 @@
+with-milli = 1987-07-5T17:45:00.12Z
diff --git a/src/test/invalid/duplicate-key-table.toml b/src/test/invalid/duplicate-key-table.toml
new file mode 100644
index 0000000..cedf05f
--- /dev/null
+++ b/src/test/invalid/duplicate-key-table.toml
@@ -0,0 +1,5 @@
+[fruit]
+type = "apple"
+
+[fruit.type]
+apple = "yes"
diff --git a/src/test/invalid/duplicate-keys.toml b/src/test/invalid/duplicate-keys.toml
new file mode 100644
index 0000000..9b5aee0
--- /dev/null
+++ b/src/test/invalid/duplicate-keys.toml
@@ -0,0 +1,2 @@
+dupe = false
+dupe = true
diff --git a/src/test/invalid/duplicate-tables.toml b/src/test/invalid/duplicate-tables.toml
new file mode 100644
index 0000000..8ddf49b
--- /dev/null
+++ b/src/test/invalid/duplicate-tables.toml
@@ -0,0 +1,2 @@
+[a]
+[a]
diff --git a/src/test/invalid/empty-implicit-table.toml b/src/test/invalid/empty-implicit-table.toml
new file mode 100644
index 0000000..0cc36d0
--- /dev/null
+++ b/src/test/invalid/empty-implicit-table.toml
@@ -0,0 +1 @@
+[naughty..naughty]
diff --git a/src/test/invalid/empty-table.toml b/src/test/invalid/empty-table.toml
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/src/test/invalid/empty-table.toml
@@ -0,0 +1 @@
+[]
diff --git a/src/test/invalid/float-no-leading-zero.toml b/src/test/invalid/float-no-leading-zero.toml
new file mode 100644
index 0000000..cab76bf
--- /dev/null
+++ b/src/test/invalid/float-no-leading-zero.toml
@@ -0,0 +1,2 @@
+answer = .12345
+neganswer = -.12345
diff --git a/src/test/invalid/float-no-trailing-digits.toml b/src/test/invalid/float-no-trailing-digits.toml
new file mode 100644
index 0000000..cbff2d0
--- /dev/null
+++ b/src/test/invalid/float-no-trailing-digits.toml
@@ -0,0 +1,2 @@
+answer = 1.
+neganswer = -1.
diff --git a/src/test/invalid/key-two-equals.toml b/src/test/invalid/key-two-equals.toml
new file mode 100644
index 0000000..25a0378
--- /dev/null
+++ b/src/test/invalid/key-two-equals.toml
@@ -0,0 +1 @@
+key= = 1
diff --git a/src/test/invalid/string-bad-byte-escape.toml b/src/test/invalid/string-bad-byte-escape.toml
new file mode 100644
index 0000000..4c7be59
--- /dev/null
+++ b/src/test/invalid/string-bad-byte-escape.toml
@@ -0,0 +1 @@
+naughty = "\xAg"
diff --git a/src/test/invalid/string-bad-escape.toml b/src/test/invalid/string-bad-escape.toml
new file mode 100644
index 0000000..60acb0c
--- /dev/null
+++ b/src/test/invalid/string-bad-escape.toml
@@ -0,0 +1 @@
+invalid-escape = "This string has a bad \a escape character."
diff --git a/src/test/invalid/string-byte-escapes.toml b/src/test/invalid/string-byte-escapes.toml
new file mode 100644
index 0000000..e94452a
--- /dev/null
+++ b/src/test/invalid/string-byte-escapes.toml
@@ -0,0 +1 @@
+answer = "\x33"
diff --git a/src/test/invalid/string-no-close.toml b/src/test/invalid/string-no-close.toml
new file mode 100644
index 0000000..0c292fc
--- /dev/null
+++ b/src/test/invalid/string-no-close.toml
@@ -0,0 +1 @@
+no-ending-quote = "One time, at band camp
diff --git a/src/test/invalid/table-array-implicit.toml b/src/test/invalid/table-array-implicit.toml
new file mode 100644
index 0000000..05f2507
--- /dev/null
+++ b/src/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/src/test/invalid/table-array-malformed-bracket.toml b/src/test/invalid/table-array-malformed-bracket.toml
new file mode 100644
index 0000000..39c73b0
--- /dev/null
+++ b/src/test/invalid/table-array-malformed-bracket.toml
@@ -0,0 +1,2 @@
+[[albums]
+name = "Born to Run"
diff --git a/src/test/invalid/table-array-malformed-empty.toml b/src/test/invalid/table-array-malformed-empty.toml
new file mode 100644
index 0000000..a470ca3
--- /dev/null
+++ b/src/test/invalid/table-array-malformed-empty.toml
@@ -0,0 +1,2 @@
+[[]]
+name = "Born to Run"
diff --git a/src/test/invalid/table-nested-brackets-close.toml b/src/test/invalid/table-nested-brackets-close.toml
new file mode 100644
index 0000000..c8b5a67
--- /dev/null
+++ b/src/test/invalid/table-nested-brackets-close.toml
@@ -0,0 +1,2 @@
+[a]b]
+zyx = 42
diff --git a/src/test/invalid/table-nested-brackets-open.toml b/src/test/invalid/table-nested-brackets-open.toml
new file mode 100644
index 0000000..246d7e9
--- /dev/null
+++ b/src/test/invalid/table-nested-brackets-open.toml
@@ -0,0 +1,2 @@
+[a[b]
+zyx = 42
diff --git a/src/test/invalid/text-after-array-entries.toml b/src/test/invalid/text-after-array-entries.toml
new file mode 100644
index 0000000..1a72890
--- /dev/null
+++ b/src/test/invalid/text-after-array-entries.toml
@@ -0,0 +1,4 @@
+array = [
+  "Is there life after an array separator?", No
+  "Entry"
+]
diff --git a/src/test/invalid/text-after-integer.toml b/src/test/invalid/text-after-integer.toml
new file mode 100644
index 0000000..42de7af
--- /dev/null
+++ b/src/test/invalid/text-after-integer.toml
@@ -0,0 +1 @@
+answer = 42 the ultimate answer?
diff --git a/src/test/invalid/text-after-string.toml b/src/test/invalid/text-after-string.toml
new file mode 100644
index 0000000..c92a6f1
--- /dev/null
+++ b/src/test/invalid/text-after-string.toml
@@ -0,0 +1 @@
+string = "Is there life after strings?" No.
diff --git a/src/test/invalid/text-after-table.toml b/src/test/invalid/text-after-table.toml
new file mode 100644
index 0000000..87da9db
--- /dev/null
+++ b/src/test/invalid/text-after-table.toml
@@ -0,0 +1 @@
+[error] this shouldn't be here
diff --git a/src/test/invalid/text-before-array-separator.toml b/src/test/invalid/text-before-array-separator.toml
new file mode 100644
index 0000000..9b06a39
--- /dev/null
+++ b/src/test/invalid/text-before-array-separator.toml
@@ -0,0 +1,4 @@
+array = [
+  "Is there life before an array separator?" No,
+  "Entry"
+]
diff --git a/src/test/invalid/text-in-array.toml b/src/test/invalid/text-in-array.toml
new file mode 100644
index 0000000..a6a6c42
--- /dev/null
+++ b/src/test/invalid/text-in-array.toml
@@ -0,0 +1,5 @@
+array = [
+  "Entry 1",
+  I don't belong,
+  "Entry 2",
+]
diff --git a/src/test/mod.rs b/src/test/mod.rs
new file mode 100644
index 0000000..6f2c7bd
--- /dev/null
+++ b/src/test/mod.rs
@@ -0,0 +1,2 @@
+mod valid;
+mod invalid;
diff --git a/src/test/valid.rs b/src/test/valid.rs
new file mode 100644
index 0000000..335e908
--- /dev/null
+++ b/src/test/valid.rs
@@ -0,0 +1,163 @@
+extern crate serialize;
+
+use std::num::strconv;
+use std::collections::TreeMap;
+use self::serialize::json;
+
+use {Parser, Value, Table, String, Integer, Float, Boolean, Datetime, Array};
+
+fn to_json(toml: Value) -> json::Json {
+    fn doit(s: &str, json: json::Json) -> json::Json {
+        let mut map = box TreeMap::new();
+        map.insert("type".to_string(), json::String(s.to_string()));
+        map.insert("value".to_string(), json);
+        json::Object(map)
+    }
+    match toml {
+        String(s) => doit("string", json::String(s)),
+        Integer(i) => doit("integer", json::String(i.to_str())),
+        Float(f) => doit("float", json::String({
+            let (bytes, _) =
+                strconv::float_to_str_bytes_common(f, 10, true,
+                                                   strconv::SignNeg,
+                                                   strconv::DigMax(15),
+                                                   strconv::ExpNone,
+                                                   false);
+            let s = String::from_utf8(bytes).unwrap();
+            if s.as_slice().contains(".") {s} else {format!("{}.0", s)}
+        })),
+        Boolean(b) => doit("bool", json::String(b.to_str())),
+        Datetime(s) => doit("datetime", json::String(s)),
+        Array(arr) => {
+            let is_table = match arr.as_slice().head() {
+                Some(&Table(..)) => true,
+                _ => false,
+            };
+            let json = json::List(arr.move_iter().map(to_json).collect());
+            if is_table {json} else {doit("array", json)}
+        }
+        Table(table) => json::Object(box table.move_iter().map(|(k, v)| {
+            (k, to_json(v))
+        }).collect()),
+    }
+}
+
+fn run(toml: &str, json: &str) {
+    let mut p = Parser::new(toml);
+    let table = p.parse();
+    assert!(p.errors.len() == 0, "had_errors: {}",
+            p.errors.iter().map(|e| {
+                (e.desc.clone(), toml.slice(e.lo - 5, e.hi + 5))
+            }).collect::<Vec<(String, &str)>>());
+    assert!(table.is_some());
+    let table = table.unwrap();
+
+    let json = json::from_str(json).unwrap();
+    let toml_json = to_json(Table(table));
+    assert!(json == toml_json,
+            "expected\n{}\ngot\n{}\n",
+            json.to_pretty_str(),
+            toml_json.to_pretty_str());
+}
+
+macro_rules! test( ($name:ident, $toml:expr, $json:expr) => (
+    #[test]
+    fn $name() { run($toml, $json); }
+) )
+
+test!(array_empty,
+       include_str!("valid/array-empty.toml"),
+       include_str!("valid/array-empty.json"))
+test!(array_nospaces,
+       include_str!("valid/array-nospaces.toml"),
+       include_str!("valid/array-nospaces.json"))
+test!(arrays_hetergeneous,
+       include_str!("valid/arrays-hetergeneous.toml"),
+       include_str!("valid/arrays-hetergeneous.json"))
+test!(arrays,
+       include_str!("valid/arrays.toml"),
+       include_str!("valid/arrays.json"))
+test!(arrays_nested,
+       include_str!("valid/arrays-nested.toml"),
+       include_str!("valid/arrays-nested.json"))
+test!(empty,
+       include_str!("valid/empty.toml"),
+       include_str!("valid/empty.json"))
+test!(bool,
+       include_str!("valid/bool.toml"),
+       include_str!("valid/bool.json"))
+test!(datetime,
+       include_str!("valid/datetime.toml"),
+       include_str!("valid/datetime.json"))
+test!(example,
+       include_str!("valid/example.toml"),
+       include_str!("valid/example.json"))
+test!(float,
+       include_str!("valid/float.toml"),
+       include_str!("valid/float.json"))
+test!(implicit_and_explicit_after,
+       include_str!("valid/implicit-and-explicit-after.toml"),
+       include_str!("valid/implicit-and-explicit-after.json"))
+test!(implicit_and_explicit_before,
+       include_str!("valid/implicit-and-explicit-before.toml"),
+       include_str!("valid/implicit-and-explicit-before.json"))
+test!(implicit_groups,
+       include_str!("valid/implicit-groups.toml"),
+       include_str!("valid/implicit-groups.json"))
+test!(integer,
+       include_str!("valid/integer.toml"),
+       include_str!("valid/integer.json"))
+test!(key_equals_nospace,
+       include_str!("valid/key-equals-nospace.toml"),
+       include_str!("valid/key-equals-nospace.json"))
+test!(key_special_chars,
+       include_str!("valid/key-special-chars.toml"),
+       include_str!("valid/key-special-chars.json"))
+test!(key_with_pound,
+       include_str!("valid/key-with-pound.toml"),
+       include_str!("valid/key-with-pound.json"))
+test!(long_float,
+       include_str!("valid/long-float.toml"),
+       include_str!("valid/long-float.json"))
+test!(long_integer,
+       include_str!("valid/long-integer.toml"),
+       include_str!("valid/long-integer.json"))
+test!(string_escapes,
+       include_str!("valid/string-escapes.toml"),
+       include_str!("valid/string-escapes.json"))
+test!(string_simple,
+       include_str!("valid/string-simple.toml"),
+       include_str!("valid/string-simple.json"))
+test!(string_with_pound,
+       include_str!("valid/string-with-pound.toml"),
+       include_str!("valid/string-with-pound.json"))
+test!(table_array_implicit,
+       include_str!("valid/table-array-implicit.toml"),
+       include_str!("valid/table-array-implicit.json"))
+test!(table_array_many,
+       include_str!("valid/table-array-many.toml"),
+       include_str!("valid/table-array-many.json"))
+test!(table_array_nest,
+       include_str!("valid/table-array-nest.toml"),
+       include_str!("valid/table-array-nest.json"))
+test!(table_array_one,
+       include_str!("valid/table-array-one.toml"),
+       include_str!("valid/table-array-one.json"))
+test!(table_empty,
+       include_str!("valid/table-empty.toml"),
+       include_str!("valid/table-empty.json"))
+test!(table_sub_empty,
+       include_str!("valid/table-sub-empty.toml"),
+       include_str!("valid/table-sub-empty.json"))
+test!(table_whitespace,
+       include_str!("valid/table-whitespace.toml"),
+       include_str!("valid/table-whitespace.json"))
+test!(table_with_pound,
+       include_str!("valid/table-with-pound.toml"),
+       include_str!("valid/table-with-pound.json"))
+test!(unicode_escape,
+       include_str!("valid/unicode-escape.toml"),
+       include_str!("valid/unicode-escape.json"))
+test!(unicode_literal,
+       include_str!("valid/unicode-literal.toml"),
+       include_str!("valid/unicode-literal.json"))
diff --git a/src/test/valid/array-empty.json b/src/test/valid/array-empty.json
new file mode 100644
index 0000000..2fbf256
--- /dev/null
+++ b/src/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/src/test/valid/array-empty.toml b/src/test/valid/array-empty.toml
new file mode 100644
index 0000000..fa58dc6
--- /dev/null
+++ b/src/test/valid/array-empty.toml
@@ -0,0 +1 @@
+thevoid = [[[[[]]]]]
diff --git a/src/test/valid/array-nospaces.json b/src/test/valid/array-nospaces.json
new file mode 100644
index 0000000..1833d61
--- /dev/null
+++ b/src/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/src/test/valid/array-nospaces.toml b/src/test/valid/array-nospaces.toml
new file mode 100644
index 0000000..6618936
--- /dev/null
+++ b/src/test/valid/array-nospaces.toml
@@ -0,0 +1 @@
+ints = [1,2,3]
diff --git a/src/test/valid/arrays-hetergeneous.json b/src/test/valid/arrays-hetergeneous.json
new file mode 100644
index 0000000..e703739
--- /dev/null
+++ b/src/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.0"},
+                {"type": "float", "value": "2.0"}
+            ]}
+        ]
+    }
+}
diff --git a/src/test/valid/arrays-hetergeneous.toml b/src/test/valid/arrays-hetergeneous.toml
new file mode 100644
index 0000000..91fcbdf
--- /dev/null
+++ b/src/test/valid/arrays-hetergeneous.toml
@@ -0,0 +1 @@
+mixed = [[1, 2], ["a", "b"], [1.0, 2.0]]
diff --git a/src/test/valid/arrays-nested.json b/src/test/valid/arrays-nested.json
new file mode 100644
index 0000000..d21920c
--- /dev/null
+++ b/src/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/src/test/valid/arrays-nested.toml b/src/test/valid/arrays-nested.toml
new file mode 100644
index 0000000..ce33022
--- /dev/null
+++ b/src/test/valid/arrays-nested.toml
@@ -0,0 +1 @@
+nest = [["a"], ["b"]]
diff --git a/src/test/valid/arrays.json b/src/test/valid/arrays.json
new file mode 100644
index 0000000..4d16d8a
--- /dev/null
+++ b/src/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.0"},
+            {"type": "float", "value": "2.0"},
+            {"type": "float", "value": "3.0"}
+        ]
+    },
+    "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/src/test/valid/arrays.toml b/src/test/valid/arrays.toml
new file mode 100644
index 0000000..6d6440d
--- /dev/null
+++ b/src/test/valid/arrays.toml
@@ -0,0 +1,9 @@
+ints = [1, 2, 3]
+floats = [1.0, 2.0, 3.0]
+strings = ["a", "b", "c"]
+dates = [
+  1987-07-05T17:45:00Z,
+  1979-05-27T07:32:00Z,
+  2006-06-01T11:00:00Z,
+]
+
diff --git a/src/test/valid/bool.json b/src/test/valid/bool.json
new file mode 100644
index 0000000..ae368e9
--- /dev/null
+++ b/src/test/valid/bool.json
@@ -0,0 +1,4 @@
+{
+    "f": {"type": "bool", "value": "false"},
+    "t": {"type": "bool", "value": "true"}
+}
diff --git a/src/test/valid/bool.toml b/src/test/valid/bool.toml
new file mode 100644
index 0000000..a8a829b
--- /dev/null
+++ b/src/test/valid/bool.toml
@@ -0,0 +1,2 @@
+t = true
+f = false
diff --git a/src/test/valid/comments-everywhere.json b/src/test/valid/comments-everywhere.json
new file mode 100644
index 0000000..e69a2e9
--- /dev/null
+++ b/src/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/src/test/valid/comments-everywhere.toml b/src/test/valid/comments-everywhere.toml
new file mode 100644
index 0000000..3dca74c
--- /dev/null
+++ b/src/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/src/test/valid/datetime.json b/src/test/valid/datetime.json
new file mode 100644
index 0000000..2ca93ce
--- /dev/null
+++ b/src/test/valid/datetime.json
@@ -0,0 +1,3 @@
+{
+    "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}
+}
diff --git a/src/test/valid/datetime.toml b/src/test/valid/datetime.toml
new file mode 100644
index 0000000..2e99340
--- /dev/null
+++ b/src/test/valid/datetime.toml
@@ -0,0 +1 @@
+bestdayever = 1987-07-05T17:45:00Z
diff --git a/src/test/valid/empty.json b/src/test/valid/empty.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/src/test/valid/empty.json
@@ -0,0 +1 @@
+{}
diff --git a/src/test/valid/empty.toml b/src/test/valid/empty.toml
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/valid/example.json b/src/test/valid/example.json
new file mode 100644
index 0000000..48aa907
--- /dev/null
+++ b/src/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/src/test/valid/example.toml b/src/test/valid/example.toml
new file mode 100644
index 0000000..8cb02e0
--- /dev/null
+++ b/src/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/src/test/valid/float.json b/src/test/valid/float.json
new file mode 100644
index 0000000..b8a2e97
--- /dev/null
+++ b/src/test/valid/float.json
@@ -0,0 +1,4 @@
+{
+    "pi": {"type": "float", "value": "3.14"},
+    "negpi": {"type": "float", "value": "-3.14"}
+}
diff --git a/src/test/valid/float.toml b/src/test/valid/float.toml
new file mode 100644
index 0000000..7c528d2
--- /dev/null
+++ b/src/test/valid/float.toml
@@ -0,0 +1,2 @@
+pi = 3.14
+negpi = -3.14
diff --git a/src/test/valid/implicit-and-explicit-after.json b/src/test/valid/implicit-and-explicit-after.json
new file mode 100644
index 0000000..374bd09
--- /dev/null
+++ b/src/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/src/test/valid/implicit-and-explicit-after.toml b/src/test/valid/implicit-and-explicit-after.toml
new file mode 100644
index 0000000..c0e8865
--- /dev/null
+++ b/src/test/valid/implicit-and-explicit-after.toml
@@ -0,0 +1,5 @@
+[a.b.c]
+answer = 42
+
+[a]
+better = 43
diff --git a/src/test/valid/implicit-and-explicit-before.json b/src/test/valid/implicit-and-explicit-before.json
new file mode 100644
index 0000000..374bd09
--- /dev/null
+++ b/src/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/src/test/valid/implicit-and-explicit-before.toml b/src/test/valid/implicit-and-explicit-before.toml
new file mode 100644
index 0000000..eee68ff
--- /dev/null
+++ b/src/test/valid/implicit-and-explicit-before.toml
@@ -0,0 +1,5 @@
+[a]
+better = 43
+
+[a.b.c]
+answer = 42
diff --git a/src/test/valid/implicit-groups.json b/src/test/valid/implicit-groups.json
new file mode 100644
index 0000000..fbae7fc
--- /dev/null
+++ b/src/test/valid/implicit-groups.json
@@ -0,0 +1,9 @@
+{
+    "a": {
+        "b": {
+            "c": {
+                "answer": {"type": "integer", "value": "42"}
+            }
+        }
+    }
+}
diff --git a/src/test/valid/implicit-groups.toml b/src/test/valid/implicit-groups.toml
new file mode 100644
index 0000000..b6333e4
--- /dev/null
+++ b/src/test/valid/implicit-groups.toml
@@ -0,0 +1,2 @@
+[a.b.c]
+answer = 42
diff --git a/src/test/valid/integer.json b/src/test/valid/integer.json
new file mode 100644
index 0000000..61985a1
--- /dev/null
+++ b/src/test/valid/integer.json
@@ -0,0 +1,4 @@
+{
+    "answer": {"type": "integer", "value": "42"},
+    "neganswer": {"type": "integer", "value": "-42"}
+}
diff --git a/src/test/valid/integer.toml b/src/test/valid/integer.toml
new file mode 100644
index 0000000..c4f6297
--- /dev/null
+++ b/src/test/valid/integer.toml
@@ -0,0 +1,2 @@
+answer = 42
+neganswer = -42
diff --git a/src/test/valid/key-equals-nospace.json b/src/test/valid/key-equals-nospace.json
new file mode 100644
index 0000000..1f8709a
--- /dev/null
+++ b/src/test/valid/key-equals-nospace.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "integer", "value": "42"}
+}
diff --git a/src/test/valid/key-equals-nospace.toml b/src/test/valid/key-equals-nospace.toml
new file mode 100644
index 0000000..560901c
--- /dev/null
+++ b/src/test/valid/key-equals-nospace.toml
@@ -0,0 +1 @@
+answer=42
diff --git a/src/test/valid/key-special-chars.json b/src/test/valid/key-special-chars.json
new file mode 100644
index 0000000..6550ebd
--- /dev/null
+++ b/src/test/valid/key-special-chars.json
@@ -0,0 +1,5 @@
+{
+    "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:'": {
+        "type": "integer", "value": "1"
+    }
+}
diff --git a/src/test/valid/key-special-chars.toml b/src/test/valid/key-special-chars.toml
new file mode 100644
index 0000000..8b3fc51
--- /dev/null
+++ b/src/test/valid/key-special-chars.toml
@@ -0,0 +1 @@
+~!@#$^&*()_+-`1234567890[]\|/?><.,;:' = 1
diff --git a/src/test/valid/key-with-pound.json b/src/test/valid/key-with-pound.json
new file mode 100644
index 0000000..ee39e1d
--- /dev/null
+++ b/src/test/valid/key-with-pound.json
@@ -0,0 +1,3 @@
+{
+    "key#name": {"type": "integer", "value": "5"}
+}
diff --git a/src/test/valid/key-with-pound.toml b/src/test/valid/key-with-pound.toml
new file mode 100644
index 0000000..1c54f53
--- /dev/null
+++ b/src/test/valid/key-with-pound.toml
@@ -0,0 +1 @@
+key#name = 5
diff --git a/src/test/valid/long-float.json b/src/test/valid/long-float.json
new file mode 100644
index 0000000..8ceed47
--- /dev/null
+++ b/src/test/valid/long-float.json
@@ -0,0 +1,4 @@
+{
+    "longpi": {"type": "float", "value": "3.141592653589793"},
+    "neglongpi": {"type": "float", "value": "-3.141592653589793"}
+}
diff --git a/src/test/valid/long-float.toml b/src/test/valid/long-float.toml
new file mode 100644
index 0000000..9558ae4
--- /dev/null
+++ b/src/test/valid/long-float.toml
@@ -0,0 +1,2 @@
+longpi = 3.141592653589793
+neglongpi = -3.141592653589793
diff --git a/src/test/valid/long-integer.json b/src/test/valid/long-integer.json
new file mode 100644
index 0000000..16c331e
--- /dev/null
+++ b/src/test/valid/long-integer.json
@@ -0,0 +1,4 @@
+{
+    "answer": {"type": "integer", "value": "9223372036854775807"},
+    "neganswer": {"type": "integer", "value": "-9223372036854775808"}
+}
diff --git a/src/test/valid/long-integer.toml b/src/test/valid/long-integer.toml
new file mode 100644
index 0000000..424a13a
--- /dev/null
+++ b/src/test/valid/long-integer.toml
@@ -0,0 +1,2 @@
+answer = 9223372036854775807
+neganswer = -9223372036854775808
diff --git a/src/test/valid/string-escapes.json b/src/test/valid/string-escapes.json
new file mode 100644
index 0000000..ca71d30
--- /dev/null
+++ b/src/test/valid/string-escapes.json
@@ -0,0 +1,34 @@
+{
+    "backspace": {
+        "type": "string",
+        "value": "This string has a \u0008 backspace character."
+    },
+    "tab": {
+        "type": "string",
+        "value": "This string has a \u0009 tab character."
+    },
+    "newline": {
+        "type": "string",
+        "value": "This string has a \u000A new line character."
+    },
+    "formfeed": {
+        "type": "string",
+        "value": "This string has a \u000C form feed character."
+    },
+    "carriage": {
+        "type": "string",
+        "value": "This string has a \u000D carriage return character."
+    },
+    "quote": {
+        "type": "string",
+        "value": "This string has a \u0022 quote character."
+    },
+    "slash": {
+        "type": "string",
+        "value": "This string has a \u002F slash character."
+    },
+    "backslash": {
+        "type": "string",
+        "value": "This string has a \u005C backslash character."
+    }
+}
diff --git a/src/test/valid/string-escapes.toml b/src/test/valid/string-escapes.toml
new file mode 100644
index 0000000..2d64500
--- /dev/null
+++ b/src/test/valid/string-escapes.toml
@@ -0,0 +1,8 @@
+backspace = "This string has a \b backspace character."
+tab = "This string has a \t tab character."
+newline = "This string has a \n new line character."
+formfeed = "This string has a \f form feed character."
+carriage = "This string has a \r carriage return character."
+quote = "This string has a \" quote character."
+slash = "This string has a \/ slash character."
+backslash = "This string has a \\ backslash character."
diff --git a/src/test/valid/string-simple.json b/src/test/valid/string-simple.json
new file mode 100644
index 0000000..2e05f99
--- /dev/null
+++ b/src/test/valid/string-simple.json
@@ -0,0 +1,6 @@
+{
+    "answer": {
+        "type": "string",
+        "value": "You are not drinking enough whisky."
+    }
+}
diff --git a/src/test/valid/string-simple.toml b/src/test/valid/string-simple.toml
new file mode 100644
index 0000000..e17ade6
--- /dev/null
+++ b/src/test/valid/string-simple.toml
@@ -0,0 +1 @@
+answer = "You are not drinking enough whisky."
diff --git a/src/test/valid/string-with-pound.json b/src/test/valid/string-with-pound.json
new file mode 100644
index 0000000..33cdc9c
--- /dev/null
+++ b/src/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/src/test/valid/string-with-pound.toml b/src/test/valid/string-with-pound.toml
new file mode 100644
index 0000000..5fd8746
--- /dev/null
+++ b/src/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/src/test/valid/table-array-implicit.json b/src/test/valid/table-array-implicit.json
new file mode 100644
index 0000000..32e4640
--- /dev/null
+++ b/src/test/valid/table-array-implicit.json
@@ -0,0 +1,7 @@
+{
+    "albums": {
+       "songs": [
+           {"name": {"type": "string", "value": "Glory Days"}}
+       ]
+    }
+}
diff --git a/src/test/valid/table-array-implicit.toml b/src/test/valid/table-array-implicit.toml
new file mode 100644
index 0000000..3157ac9
--- /dev/null
+++ b/src/test/valid/table-array-implicit.toml
@@ -0,0 +1,2 @@
+[[albums.songs]]
+name = "Glory Days"
diff --git a/src/test/valid/table-array-many.json b/src/test/valid/table-array-many.json
new file mode 100644
index 0000000..84df2da
--- /dev/null
+++ b/src/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/src/test/valid/table-array-many.toml b/src/test/valid/table-array-many.toml
new file mode 100644
index 0000000..46062be
--- /dev/null
+++ b/src/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/src/test/valid/table-array-nest.json b/src/test/valid/table-array-nest.json
new file mode 100644
index 0000000..c117afa
--- /dev/null
+++ b/src/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/src/test/valid/table-array-nest.toml b/src/test/valid/table-array-nest.toml
new file mode 100644
index 0000000..d659a3d
--- /dev/null
+++ b/src/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/src/test/valid/table-array-one.json b/src/test/valid/table-array-one.json
new file mode 100644
index 0000000..d75faae
--- /dev/null
+++ b/src/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/src/test/valid/table-array-one.toml b/src/test/valid/table-array-one.toml
new file mode 100644
index 0000000..cd7e1b6
--- /dev/null
+++ b/src/test/valid/table-array-one.toml
@@ -0,0 +1,3 @@
+[[people]]
+first_name = "Bruce"
+last_name = "Springsteen"
diff --git a/src/test/valid/table-empty.json b/src/test/valid/table-empty.json
new file mode 100644
index 0000000..6f3873a
--- /dev/null
+++ b/src/test/valid/table-empty.json
@@ -0,0 +1,3 @@
+{
+    "a": {}
+}
diff --git a/src/test/valid/table-empty.toml b/src/test/valid/table-empty.toml
new file mode 100644
index 0000000..8bb6a0a
--- /dev/null
+++ b/src/test/valid/table-empty.toml
@@ -0,0 +1 @@
+[a]
diff --git a/src/test/valid/table-sub-empty.json b/src/test/valid/table-sub-empty.json
new file mode 100644
index 0000000..9787770
--- /dev/null
+++ b/src/test/valid/table-sub-empty.json
@@ -0,0 +1,3 @@
+{
+    "a": { "b": {} }
+}
diff --git a/src/test/valid/table-sub-empty.toml b/src/test/valid/table-sub-empty.toml
new file mode 100644
index 0000000..70b7fe1
--- /dev/null
+++ b/src/test/valid/table-sub-empty.toml
@@ -0,0 +1,2 @@
+[a]
+[a.b]
diff --git a/src/test/valid/table-whitespace.json b/src/test/valid/table-whitespace.json
new file mode 100644
index 0000000..3a73ec8
--- /dev/null
+++ b/src/test/valid/table-whitespace.json
@@ -0,0 +1,3 @@
+{
+    "valid key": {}
+}
diff --git a/src/test/valid/table-whitespace.toml b/src/test/valid/table-whitespace.toml
new file mode 100644
index 0000000..798756c
--- /dev/null
+++ b/src/test/valid/table-whitespace.toml
@@ -0,0 +1 @@
+[valid key]
diff --git a/src/test/valid/table-with-pound.json b/src/test/valid/table-with-pound.json
new file mode 100644
index 0000000..5e594e4
--- /dev/null
+++ b/src/test/valid/table-with-pound.json
@@ -0,0 +1,5 @@
+{
+    "key#group": {
+        "answer": {"type": "integer", "value": "42"}
+    }
+}
diff --git a/src/test/valid/table-with-pound.toml b/src/test/valid/table-with-pound.toml
new file mode 100644
index 0000000..e7b777e
--- /dev/null
+++ b/src/test/valid/table-with-pound.toml
@@ -0,0 +1,2 @@
+[key#group]
+answer = 42
diff --git a/src/test/valid/unicode-escape.json b/src/test/valid/unicode-escape.json
new file mode 100644
index 0000000..deda62c
--- /dev/null
+++ b/src/test/valid/unicode-escape.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "string", "value": "\u03B4"}
+}
diff --git a/src/test/valid/unicode-escape.toml b/src/test/valid/unicode-escape.toml
new file mode 100644
index 0000000..057ce15
--- /dev/null
+++ b/src/test/valid/unicode-escape.toml
@@ -0,0 +1 @@
+answer = "\u03B4"
diff --git a/src/test/valid/unicode-literal.json b/src/test/valid/unicode-literal.json
new file mode 100644
index 0000000..00aa2f8
--- /dev/null
+++ b/src/test/valid/unicode-literal.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "string", "value": "δ"}
+}
diff --git a/src/test/valid/unicode-literal.toml b/src/test/valid/unicode-literal.toml
new file mode 100644
index 0000000..c65723c
--- /dev/null
+++ b/src/test/valid/unicode-literal.toml
@@ -0,0 +1 @@
+answer = "δ"
diff --git a/src/toml.rs b/src/toml.rs
new file mode 100644
index 0000000..cebdb37
--- /dev/null
+++ b/src/toml.rs
@@ -0,0 +1,51 @@
+#![crate_type = "lib"]
+#![feature(macro_rules)]
+
+use std::collections::HashMap;
+
+pub use parser::{Parser, Error};
+
+mod parser;
+#[cfg(test)]
+mod test;
+
+pub enum Value {
+    String(String),
+    Integer(i64),
+    Float(f64),
+    Boolean(bool),
+    Datetime(String),
+    Array(Array),
+    Table(Table),
+}
+
+pub type Array = Vec<Value>;
+pub type Table = HashMap<String, Value>;
+
+impl Value {
+    fn same_type(&self, other: &Value) -> bool {
+        match (self, other) {
+            (&String(..), &String(..)) |
+            (&Integer(..), &Integer(..)) |
+            (&Float(..), &Float(..)) |
+            (&Boolean(..), &Boolean(..)) |
+            (&Datetime(..), &Datetime(..)) |
+            (&Array(..), &Array(..)) |
+            (&Table(..), &Table(..)) => true,
+
+            _ => false,
+        }
+    }
+
+    fn type_str(&self) -> &'static str {
+        match *self {
+            String(..) => "string",
+            Integer(..) => "integer",
+            Float(..) => "float",
+            Boolean(..) => "boolean",
+            Datetime(..) => "datetime",
+            Array(..) => "array",
+            Table(..) => "table",
+        }
+    }
+}
-- 
cgit v1.2.3