Repository: toml-rs/toml (language: rust)

Issue:
style: Update for latest clippy
Noticed this in #309 
fix(toml_edit): Fuzz build error
This fixes `crates/toml_edit/fuzz` not included in workspace error.
perf(parser): Batch create strings
The grammar has us build strings a character-at-a-time but a small tweak
to the grammar allows us to build it in chunks of non-escaped content.

It looks like this only cut `minimal`s time by 2%
TableLike::fmt does not format dotted keyed-items
`TableLike::fmt` acts on the toml document table, and not on the in-memory data structure, so we limit it only to values.  However, since we've added dotted keys, they also fall into that definition but we aren't reformatting them.
Help users who mistakenly use bare words
We are more closely matching toml-rs's behavior for error reporting so anything that doesn't look like a number or date, we'll tell the user we are expecting a quoted string.

The one caveat is that this isn't solved for arrays.  The challenge is we have to use `attempt` on the `value` parser which throws the error away and they get told to use a comment instead.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 24d789ab..e1496af0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@ on:
 jobs:
   ci:
     name: CI
-    needs: [test, msrv, docs, rustfmt, clippy]
+    needs: [test, msrv, docs, rustfmt]
     runs-on: ubuntu-latest
     steps:
       - name: Done
@@ -33,13 +33,15 @@ jobs:
         override: true
     - uses: Swatinem/rust-cache@v1
     - name: Build
-      run: cargo test --no-run --workspace --all-features
+      run: cargo test --no-run --workspace
     - name: Default features
       run: cargo test --workspace
-    - name: All features
-      run: cargo test --workspace --all-features
-    - name: No-default features
-      run: cargo test --workspace --no-default-features
+    - name: toml_edit (all features)
+      run: cargo test -p toml_edit --all-features
+    - name: toml_edit (no-default features)
+      run: cargo test -p toml_edit --no-default-features
+    - name: toml (preserve_order)
+      run: cargo test -p toml --features preserve_order
   msrv:
     name: "Check MSRV: 1.59.0"
     runs-on: ubuntu-latest
@@ -94,21 +96,21 @@ jobs:
     - uses: Swatinem/rust-cache@v1
     - name: Check formatting
       run: cargo fmt --all -- --check
-  clippy:
-    name: clippy
-    runs-on: ubuntu-latest
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v3
-    - name: Install Rust
-      uses: actions-rs/toolchain@v1
-      with:
-        toolchain: 1.59.0  # MSRV
-        profile: minimal
-        override: true
-        components: clippy
-    - uses: Swatinem/rust-cache@v1
-    - uses: actions-rs/clippy-check@v1
-      with:
-        token: ${{ secrets.GITHUB_TOKEN }}
-        args: --workspace --all-features --all-targets -- -D warnings --allow deprecated
+#  clippy:
+#    name: clippy
+#    runs-on: ubuntu-latest
+#    steps:
+#    - name: Checkout repository
+#      uses: actions/checkout@v3
+#    - name: Install Rust
+#      uses: actions-rs/toolchain@v1
+#      with:
+#        toolchain: 1.59.0  # MSRV
+#        profile: minimal
+#        override: true
+#        components: clippy
+#    - uses: Swatinem/rust-cache@v1
+#    - uses: actions-rs/clippy-check@v1
+#      with:
+#        token: ${{ secrets.GITHUB_TOKEN }}
+#        args: --workspace --all-features --all-targets -- -D warnings --allow deprecated
diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml
index 34365c27..7ef72a6b 100644
--- a/.github/workflows/rust-next.yml
+++ b/.github/workflows/rust-next.yml
@@ -24,12 +24,16 @@ jobs:
         profile: minimal
         override: true
     - uses: Swatinem/rust-cache@v1
+    - name: Build
+      run: cargo test --no-run --workspace
     - name: Default features
       run: cargo test --workspace
-    - name: All features
-      run: cargo test --workspace --all-features
-    - name: No-default features
-      run: cargo test --workspace --no-default-features
+    - name: toml_edit (all features)
+      run: cargo test -p toml_edit --all-features
+    - name: toml_edit (no-default features)
+      run: cargo test -p toml_edit --no-default-features
+    - name: toml (preserve_order)
+      run: cargo test -p toml --features preserve_order
   rustfmt:
     name: rustfmt
     strategy:
@@ -52,27 +56,27 @@ jobs:
     - uses: Swatinem/rust-cache@v1
     - name: Check formatting
       run: cargo fmt --all -- --check
-  clippy:
-    name: clippy
-    strategy:
-      matrix:
-        rust:
-        - 1.59.0  # MSRV
-        - stable
-    continue-on-error: ${{ matrix.rust != '1.59.0' }}  # MSRV
-    runs-on: ubuntu-latest
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v3
-    - name: Install Rust
-      uses: actions-rs/toolchain@v1
-      with:
-        toolchain: ${{ matrix.rust }}
-        profile: minimal
-        override: true
-        components: clippy
-    - uses: Swatinem/rust-cache@v1
-    - uses: actions-rs/clippy-check@v1
-      with:
-        token: ${{ secrets.GITHUB_TOKEN }}
-        args: --workspace --all-features --all-targets -- -D warnings
+#  clippy:
+#    name: clippy
+#    strategy:
+#      matrix:
+#        rust:
+#        - 1.59.0  # MSRV
+#        - stable
+#    continue-on-error: ${{ matrix.rust != '1.59.0' }}  # MSRV
+#    runs-on: ubuntu-latest
+#    steps:
+#    - name: Checkout repository
+#      uses: actions/checkout@v3
+#    - name: Install Rust
+#      uses: actions-rs/toolchain@v1
+#      with:
+#        toolchain: ${{ matrix.rust }}
+#        profile: minimal
+#        override: true
+#        components: clippy
+#    - uses: Swatinem/rust-cache@v1
+#    - uses: actions-rs/clippy-check@v1
+#      with:
+#        token: ${{ secrets.GITHUB_TOKEN }}
+#        args: --workspace --all-features --all-targets -- -D warnings
diff --git a/Cargo.toml b/Cargo.toml
index 0e0eb6cf..735bfb14 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,88 +1,5 @@
-[package]
-name = "toml_edit"
-version = "0.14.4"
-readme = "README.md"
-license = "MIT/Apache-2.0"
-keywords = ["toml"]
-categories = ["encoding", "parser-implementations", "parsing"]
-description = "Yet another format-preserving TOML parser."
-authors = ["Andronik Ordian <write@reusable.software>", "Ed Page <eopage@gmail.com>"]
-repository = "https://github.com/ordian/toml_edit"
-documentation = "https://docs.rs/toml_edit"
-edition = "2018"
-include = [
-  "src/**/*",
-  "Cargo.toml",
-  "LICENSE*",
-  "README.md",
-  "examples/**/*",
-  "benches/**/*",
-  "tests/**/*"
-]
-
-[package.metadata.docs.rs]
-features = ["easy"]
-
-[package.metadata.release]
-pre-release-replacements = [
-  {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
-  {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1},
-  {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
-  {file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## [Unreleased] - ReleaseDate\n", exactly=1},
-  {file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/ordian/toml_edit/compare/{{tag_name}}...HEAD", exactly=1},
-]
-
-[features]
-default = []
-easy = ["serde"]
-perf = ["kstring"]
-
-[dependencies]
-indexmap = "1.7"
-combine = "4.6.3"
-itertools = "0.10"
-serde = { version = "1", features = ["derive"], optional = true }
-kstring = { version = "2.0.0", features = ["max_inline"], optional = true }
-
-[dev-dependencies]
-serde_json = "1.0.44"
-pretty_assertions = "1.0.0"
-toml-test-harness = "0.3"
-snapbox = { version = "0.3.3", features = ["harness"] }
-criterion = "0.3"
-toml = "0.5"
-
-[[test]]
-name = "decoder_compliance"
-harness = false
-
-[[test]]
-name = "encoder_compliance"
-harness = false
-
-[[test]]
-name = "easy_decoder_compliance"
-harness = false
-
-[[test]]
-name = "easy_encoder_compliance"
-harness = false
-
-[[test]]
-name = "test_invalid"
-harness = false
-
-[[bench]]
-name = "cargo"
-harness = false
-
-[[bench]]
-name = "linear"
-harness = false
-
-[[example]]
-name = "visit"
-test = true
+[workspace]
+members = ["crates/*"]
 
 [profile.release]
 debug = 1
diff --git a/crates/toml/Cargo.toml b/crates/toml/Cargo.toml
new file mode 100644
index 00000000..32627c20
--- /dev/null
+++ b/crates/toml/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "toml"
+version = "0.5.9"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+license = "MIT/Apache-2.0"
+readme = "README.md"
+keywords = ["encoding"]
+repository = "https://github.com/alexcrichton/toml-rs"
+homepage = "https://github.com/alexcrichton/toml-rs"
+documentation = "https://docs.rs/toml"
+description = """
+A native Rust encoder and decoder of TOML-formatted files and streams. Provides
+implementations of the standard Serialize/Deserialize traits for TOML data to
+facilitate deserializing and serializing Rust structures.
+"""
+categories = ["config", "encoding", "parser-implementations"]
+edition = "2018"
+
+[dependencies]
+serde = "1.0.97"
+indexmap = { version = "1.0", optional = true }
+
+[dev-dependencies]
+serde_derive = "1.0"
+serde_json = "1.0"
+
+[features]
+default = []
+
+# Use indexmap rather than BTreeMap as the map type of toml::Value.
+# This allows data to be read into a Value and written back to a TOML string
+# while preserving the order of map keys in the input.
+preserve_order = ["indexmap"]
diff --git a/LICENSE-APACHE b/crates/toml/LICENSE-APACHE
similarity index 100%
rename from LICENSE-APACHE
rename to crates/toml/LICENSE-APACHE
diff --git a/crates/toml/LICENSE-MIT b/crates/toml/LICENSE-MIT
new file mode 100644
index 00000000..39e0ed66
--- /dev/null
+++ b/crates/toml/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2014 Alex Crichton
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/crates/toml/README.md b/crates/toml/README.md
new file mode 100644
index 00000000..21dd1088
--- /dev/null
+++ b/crates/toml/README.md
@@ -0,0 +1,38 @@
+# toml-rs
+
+[![Latest Version](https://img.shields.io/crates/v/toml.svg)](https://crates.io/crates/toml)
+[![Documentation](https://docs.rs/toml/badge.svg)](https://docs.rs/toml)
+
+A [TOML][toml] decoder and encoder for Rust. This library is currently compliant
+with the v0.5.0 version of TOML. This library will also likely continue to stay
+up to date with the TOML specification as changes happen.
+
+[toml]: https://github.com/toml-lang/toml
+
+```toml
+# Cargo.toml
+[dependencies]
+toml = "0.5"
+```
+
+This crate also supports serialization/deserialization through the
+[serde](https://serde.rs) crate on crates.io. Currently the older `rustc-serialize`
+crate is not supported in the 0.3+ series of the `toml` crate, but 0.2 can be
+used for that support.
+
+# License
+
+This project is licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
+   http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or
+   http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in toml-rs by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
diff --git a/crates/toml/examples/decode.rs b/crates/toml/examples/decode.rs
new file mode 100644
index 00000000..256069b3
--- /dev/null
+++ b/crates/toml/examples/decode.rs
@@ -0,0 +1,54 @@
+//! An example showing off the usage of `Deserialize` to automatically decode
+//! TOML into a Rust `struct`
+
+#![deny(warnings)]
+#![allow(dead_code)]
+
+use serde_derive::Deserialize;
+
+/// This is what we're going to decode into. Each field is optional, meaning
+/// that it doesn't have to be present in TOML.
+#[derive(Debug, Deserialize)]
+struct Config {
+    global_string: Option<String>,
+    global_integer: Option<u64>,
+    server: Option<ServerConfig>,
+    peers: Option<Vec<PeerConfig>>,
+}
+
+/// Sub-structs are decoded from tables, so this will decode from the `[server]`
+/// table.
+///
+/// Again, each field is optional, meaning they don't have to be present.
+#[derive(Debug, Deserialize)]
+struct ServerConfig {
+    ip: Option<String>,
+    port: Option<u64>,
+}
+
+#[derive(Debug, Deserialize)]
+struct PeerConfig {
+    ip: Option<String>,
+    port: Option<u64>,
+}
+
+fn main() {
+    let toml_str = r#"
+        global_string = "test"
+        global_integer = 5
+
+        [server]
+        ip = "127.0.0.1"
+        port = 80
+
+        [[peers]]
+        ip = "127.0.0.1"
+        port = 8080
+
+        [[peers]]
+        ip = "127.0.0.1"
+    "#;
+
+    let decoded: Config = toml::from_str(toml_str).unwrap();
+    println!("{:#?}", decoded);
+}
diff --git a/crates/toml/examples/enum_external.rs b/crates/toml/examples/enum_external.rs
new file mode 100644
index 00000000..7de061f6
--- /dev/null
+++ b/crates/toml/examples/enum_external.rs
@@ -0,0 +1,45 @@
+//! An example showing off the usage of `Deserialize` to automatically decode
+//! TOML into a Rust `struct`, with enums.
+
+#![deny(warnings)]
+#![allow(dead_code)]
+
+use serde_derive::Deserialize;
+
+/// This is what we're going to decode into.
+#[derive(Debug, Deserialize)]
+struct Config {
+    plain: MyEnum,
+    plain_table: MyEnum,
+    tuple: MyEnum,
+    #[serde(rename = "struct")]
+    structv: MyEnum,
+    newtype: MyEnum,
+    my_enum: Vec<MyEnum>,
+}
+
+#[derive(Debug, Deserialize)]
+enum MyEnum {
+    Plain,
+    Tuple(i64, bool),
+    NewType(String),
+    Struct { value: i64 },
+}
+
+fn main() {
+    let toml_str = r#"
+    plain = "Plain"
+    plain_table = { Plain = {} }
+    tuple = { Tuple = { 0 = 123, 1 = true } }
+    struct = { Struct = { value = 123 } }
+    newtype = { NewType = "value" }
+    my_enum = [
+        { Plain = {} },
+        { Tuple = { 0 = 123, 1 = true } },
+        { NewType = "value" },
+        { Struct = { value = 123 } }
+    ]"#;
+
+    let decoded: Config = toml::from_str(toml_str).unwrap();
+    println!("{:#?}", decoded);
+}
diff --git a/crates/toml/examples/toml2json.rs b/crates/toml/examples/toml2json.rs
new file mode 100644
index 00000000..1b90c9fd
--- /dev/null
+++ b/crates/toml/examples/toml2json.rs
@@ -0,0 +1,47 @@
+#![deny(warnings)]
+
+use std::env;
+use std::fs::File;
+use std::io;
+use std::io::prelude::*;
+
+use serde_json::Value as Json;
+use toml::Value as Toml;
+
+fn main() {
+    let mut args = env::args();
+    let mut input = String::new();
+    if args.len() > 1 {
+        let name = args.nth(1).unwrap();
+        File::open(&name)
+            .and_then(|mut f| f.read_to_string(&mut input))
+            .unwrap();
+    } else {
+        io::stdin().read_to_string(&mut input).unwrap();
+    }
+
+    match input.parse() {
+        Ok(toml) => {
+            let json = convert(toml);
+            println!("{}", serde_json::to_string_pretty(&json).unwrap());
+        }
+        Err(error) => println!("failed to parse TOML: {}", error),
+    }
+}
+
+fn convert(toml: Toml) -> Json {
+    match toml {
+        Toml::String(s) => Json::String(s),
+        Toml::Integer(i) => Json::Number(i.into()),
+        Toml::Float(f) => {
+            let n = serde_json::Number::from_f64(f).expect("float infinite and nan not allowed");
+            Json::Number(n)
+        }
+        Toml::Boolean(b) => Json::Bool(b),
+        Toml::Array(arr) => Json::Array(arr.into_iter().map(convert).collect()),
+        Toml::Table(table) => {
+            Json::Object(table.into_iter().map(|(k, v)| (k, convert(v))).collect())
+        }
+        Toml::Datetime(dt) => Json::String(dt.to_string()),
+    }
+}
diff --git a/crates/toml/src/datetime.rs b/crates/toml/src/datetime.rs
new file mode 100644
index 00000000..a68b0756
--- /dev/null
+++ b/crates/toml/src/datetime.rs
@@ -0,0 +1,544 @@
+use std::error;
+use std::fmt;
+use std::str::{self, FromStr};
+
+use serde::{de, ser};
+
+/// A parsed TOML datetime value
+///
+/// This structure is intended to represent the datetime primitive type that can
+/// be encoded into TOML documents. This type is a parsed version that contains
+/// all metadata internally.
+///
+/// Currently this type is intentionally conservative and only supports
+/// `to_string` as an accessor. Over time though it's intended that it'll grow
+/// more support!
+///
+/// Note that if you're using `Deserialize` to deserialize a TOML document, you
+/// can use this as a placeholder for where you're expecting a datetime to be
+/// specified.
+///
+/// Also note though that while this type implements `Serialize` and
+/// `Deserialize` it's only recommended to use this type with the TOML format,
+/// otherwise encoded in other formats it may look a little odd.
+///
+/// Depending on how the option values are used, this struct will correspond
+/// with one of the following four datetimes from the [TOML v1.0.0 spec]:
+///
+/// | `date`    | `time`    | `offset`  | TOML type          |
+/// | --------- | --------- | --------- | ------------------ |
+/// | `Some(_)` | `Some(_)` | `Some(_)` | [Offset Date-Time] |
+/// | `Some(_)` | `Some(_)` | `None`    | [Local Date-Time]  |
+/// | `Some(_)` | `None`    | `None`    | [Local Date]       |
+/// | `None`    | `Some(_)` | `None`    | [Local Time]       |
+///
+/// **1. Offset Date-Time**: If all the optional values are used, `Datetime`
+/// corresponds to an [Offset Date-Time]. From the TOML v1.0.0 spec:
+///
+/// > To unambiguously represent a specific instant in time, you may use an
+/// > RFC 3339 formatted date-time with offset.
+/// >
+/// > ```toml
+/// > odt1 = 1979-05-27T07:32:00Z
+/// > odt2 = 1979-05-27T00:32:00-07:00
+/// > odt3 = 1979-05-27T00:32:00.999999-07:00
+/// > ```
+/// >
+/// > For the sake of readability, you may replace the T delimiter between date
+/// > and time with a space character (as permitted by RFC 3339 section 5.6).
+/// >
+/// > ```toml
+/// > odt4 = 1979-05-27 07:32:00Z
+/// > ```
+///
+/// **2. Local Date-Time**: If `date` and `time` are given but `offset` is
+/// `None`, `Datetime` corresponds to a [Local Date-Time]. From the spec:
+///
+/// > If you omit the offset from an RFC 3339 formatted date-time, it will
+/// > represent the given date-time without any relation to an offset or
+/// > timezone. It cannot be converted to an instant in time without additional
+/// > information. Conversion to an instant, if required, is implementation-
+/// > specific.
+/// >
+/// > ```toml
+/// > ldt1 = 1979-05-27T07:32:00
+/// > ldt2 = 1979-05-27T00:32:00.999999
+/// > ```
+///
+/// **3. Local Date**: If only `date` is given, `Datetime` corresponds to a
+/// [Local Date]; see the docs for [`Date`].
+///
+/// **4. Local Time**: If only `time` is given, `Datetime` corresponds to a
+/// [Local Time]; see the docs for [`Time`].
+///
+/// [TOML v1.0.0 spec]: https://toml.io/en/v1.0.0
+/// [Offset Date-Time]: https://toml.io/en/v1.0.0#offset-date-time
+/// [Local Date-Time]: https://toml.io/en/v1.0.0#local-date-time
+/// [Local Date]: https://toml.io/en/v1.0.0#local-date
+/// [Local Time]: https://toml.io/en/v1.0.0#local-time
+#[derive(PartialEq, Clone)]
+pub struct Datetime {
+    /// Optional date.
+    /// Required for: *Offset Date-Time*, *Local Date-Time*, *Local Date*.
+    pub date: Option<Date>,
+
+    /// Optional time.
+    /// Required for: *Offset Date-Time*, *Local Date-Time*, *Local Time*.
+    pub time: Option<Time>,
+
+    /// Optional offset.
+    /// Required for: *Offset Date-Time*.
+    pub offset: Option<Offset>,
+}
+
+/// Error returned from parsing a `Datetime` in the `FromStr` implementation.
+#[derive(Debug, Clone)]
+pub struct DatetimeParseError {
+    _private: (),
+}
+
+// Currently serde itself doesn't have a datetime type, so we map our `Datetime`
+// to a special valid in the serde data model. Namely one with these special
+// fields/struct names.
+//
+// In general the TOML encoder/decoder will catch this and not literally emit
+// these strings but rather emit datetimes as they're intended.
+pub const FIELD: &str = "$__toml_private_datetime";
+pub const NAME: &str = "$__toml_private_Datetime";
+
+/// A parsed TOML date value
+///
+/// May be part of a [`Datetime`]. Alone, `Date` corresponds to a [Local Date].
+/// From the TOML v1.0.0 spec:
+///
+/// > If you include only the date portion of an RFC 3339 formatted date-time,
+/// > it will represent that entire day without any relation to an offset or
+/// > timezone.
+/// >
+/// > ```toml
+/// > ld1 = 1979-05-27
+/// > ```
+///
+/// [Local Date]: https://toml.io/en/v1.0.0#local-date
+#[derive(PartialEq, Clone)]
+pub struct Date {
+    /// Year: four digits
+    pub year: u16,
+    /// Month: 1 to 12
+    pub month: u8,
+    /// Day: 1 to {28, 29, 30, 31} (based on month/year)
+    pub day: u8,
+}
+
+/// A parsed TOML time value
+///
+/// May be part of a [`Datetime`]. Alone, `Time` corresponds to a [Local Time].
+/// From the TOML v1.0.0 spec:
+///
+/// > If you include only the time portion of an RFC 3339 formatted date-time,
+/// > it will represent that time of day without any relation to a specific
+/// > day or any offset or timezone.
+/// >
+/// > ```toml
+/// > lt1 = 07:32:00
+/// > lt2 = 00:32:00.999999
+/// > ```
+/// >
+/// > Millisecond precision is required. Further precision of fractional
+/// > seconds is implementation-specific. If the value contains greater
+/// > precision than the implementation can support, the additional precision
+/// > must be truncated, not rounded.
+///
+/// [Local Time]: https://toml.io/en/v1.0.0#local-time
+#[derive(PartialEq, Clone)]
+pub struct Time {
+    /// Hour: 0 to 23
+    pub hour: u8,
+    /// Minute: 0 to 59
+    pub minute: u8,
+    /// Second: 0 to {58, 59, 60} (based on leap second rules)
+    pub second: u8,
+    /// Nanosecond: 0 to 999_999_999
+    pub nanosecond: u32,
+}
+
+/// A parsed TOML time offset
+///
+#[derive(PartialEq, Clone)]
+pub enum Offset {
+    /// > A suffix which, when applied to a time, denotes a UTC offset of 00:00;
+    /// > often spoken "Zulu" from the ICAO phonetic alphabet representation of
+    /// > the letter "Z". --- [RFC 3339 section 2]
+    ///
+    /// [RFC 3339 section 2]: https://datatracker.ietf.org/doc/html/rfc3339#section-2
+    Z,
+
+    /// Offset between local time and UTC
+    Custom {
+        /// Hours: -12 to +12
+        hours: i8,
+
+        /// Minutes: 0 to 59
+        minutes: u8,
+    },
+}
+
+impl fmt::Debug for Datetime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
+
+impl fmt::Display for Datetime {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(ref date) = self.date {
+            write!(f, "{}", date)?;
+        }
+        if let Some(ref time) = self.time {
+            if self.date.is_some() {
+                write!(f, "T")?;
+            }
+            write!(f, "{}", time)?;
+        }
+        if let Some(ref offset) = self.offset {
+            write!(f, "{}", offset)?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Display for Date {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
+    }
+}
+
+impl fmt::Display for Time {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)?;
+        if self.nanosecond != 0 {
+            let s = format!("{:09}", self.nanosecond);
+            write!(f, ".{}", s.trim_end_matches('0'))?;
+        }
+        Ok(())
+    }
+}
+
+impl fmt::Display for Offset {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match *self {
+            Offset::Z => write!(f, "Z"),
+            Offset::Custom { hours, minutes } => write!(f, "{:+03}:{:02}", hours, minutes),
+        }
+    }
+}
+
+impl FromStr for Datetime {
+    type Err = DatetimeParseError;
+
+    fn from_str(date: &str) -> Result<Datetime, DatetimeParseError> {
+        // Accepted formats:
+        //
+        // 0000-00-00T00:00:00.00Z
+        // 0000-00-00T00:00:00.00
+        // 0000-00-00
+        // 00:00:00.00
+        if date.len() < 3 {
+            return Err(DatetimeParseError { _private: () });
+        }
+        let mut offset_allowed = true;
+        let mut chars = date.chars();
+
+        // First up, parse the full date if we can
+        let full_date = if chars.clone().nth(2) == Some(':') {
+            offset_allowed = false;
+            None
+        } else {
+            let y1 = u16::from(digit(&mut chars)?);
+            let y2 = u16::from(digit(&mut chars)?);
+            let y3 = u16::from(digit(&mut chars)?);
+            let y4 = u16::from(digit(&mut chars)?);
+
+            match chars.next() {
+                Some('-') => {}
+                _ => return Err(DatetimeParseError { _private: () }),
+            }
+
+            let m1 = digit(&mut chars)?;
+            let m2 = digit(&mut chars)?;
+
+            match chars.next() {
+                Some('-') => {}
+                _ => return Err(DatetimeParseError { _private: () }),
+            }
+
+            let d1 = digit(&mut chars)?;
+            let d2 = digit(&mut chars)?;
+
+            let date = Date {
+                year: y1 * 1000 + y2 * 100 + y3 * 10 + y4,
+                month: m1 * 10 + m2,
+                day: d1 * 10 + d2,
+            };
+
+            if date.month < 1 || date.month > 12 {
+                return Err(DatetimeParseError { _private: () });
+            }
+            if date.day < 1 || date.day > 31 {
+                return Err(DatetimeParseError { _private: () });
+            }
+
+            Some(date)
+        };
+
+        // Next parse the "partial-time" if available
+        let next = chars.clone().next();
+        let partial_time = if full_date.is_some()
+            && (next == Some('T') || next == Some('t') || next == Some(' '))
+        {
+            chars.next();
+            true
+        } else {
+            full_date.is_none()
+        };
+
+        let time = if partial_time {
+            let h1 = digit(&mut chars)?;
+            let h2 = digit(&mut chars)?;
+            match chars.next() {
+                Some(':') => {}
+                _ => return Err(DatetimeParseError { _private: () }),
+            }
+            let m1 = digit(&mut chars)?;
+            let m2 = digit(&mut chars)?;
+            match chars.next() {
+                Some(':') => {}
+                _ => return Err(DatetimeParseError { _private: () }),
+            }
+            let s1 = digit(&mut chars)?;
+            let s2 = digit(&mut chars)?;
+
+            let mut nanosecond = 0;
+            if chars.clone().next() == Some('.') {
+                chars.next();
+                let whole = chars.as_str();
+
+                let mut end = whole.len();
+                for (i, byte) in whole.bytes().enumerate() {
+                    match byte {
+                        b'0'..=b'9' => {
+                            if i < 9 {
+                                let p = 10_u32.pow(8 - i as u32);
+                                nanosecond += p * u32::from(byte - b'0');
+                            }
+                        }
+                        _ => {
+                            end = i;
+                            break;
+                        }
+                    }
+                }
+                if end == 0 {
+                    return Err(DatetimeParseError { _private: () });
+                }
+                chars = whole[end..].chars();
+            }
+
+            let time = Time {
+                hour: h1 * 10 + h2,
+                minute: m1 * 10 + m2,
+                second: s1 * 10 + s2,
+                nanosecond,
+            };
+
+            if time.hour > 24 {
+                return Err(DatetimeParseError { _private: () });
+            }
+            if time.minute > 59 {
+                return Err(DatetimeParseError { _private: () });
+            }
+            if time.second > 59 {
+                return Err(DatetimeParseError { _private: () });
+            }
+            if time.nanosecond > 999_999_999 {
+                return Err(DatetimeParseError { _private: () });
+            }
+
+            Some(time)
+        } else {
+            offset_allowed = false;
+            None
+        };
+
+        // And finally, parse the offset
+        let offset = if offset_allowed {
+            let next = chars.clone().next();
+            if next == Some('Z') || next == Some('z') {
+                chars.next();
+                Some(Offset::Z)
+            } else if next.is_none() {
+                None
+            } else {
+                let sign = match next {
+                    Some('+') => 1,
+                    Some('-') => -1,
+                    _ => return Err(DatetimeParseError { _private: () }),
+                };
+                chars.next();
+                let h1 = digit(&mut chars)? as i8;
+                let h2 = digit(&mut chars)? as i8;
+                match chars.next() {
+                    Some(':') => {}
+                    _ => return Err(DatetimeParseError { _private: () }),
+                }
+                let m1 = digit(&mut chars)?;
+                let m2 = digit(&mut chars)?;
+
+                Some(Offset::Custom {
+                    hours: sign * (h1 * 10 + h2),
+                    minutes: m1 * 10 + m2,
+                })
+            }
+        } else {
+            None
+        };
+
+        // Return an error if we didn't hit eof, otherwise return our parsed
+        // date
+        if chars.next().is_some() {
+            return Err(DatetimeParseError { _private: () });
+        }
+
+        Ok(Datetime {
+            date: full_date,
+            time,
+            offset,
+        })
+    }
+}
+
+fn digit(chars: &mut str::Chars<'_>) -> Result<u8, DatetimeParseError> {
+    match chars.next() {
+        Some(c) if '0' <= c && c <= '9' => Ok(c as u8 - b'0'),
+        _ => Err(DatetimeParseError { _private: () }),
+    }
+}
+
+impl ser::Serialize for Datetime {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        use serde::ser::SerializeStruct;
+
+        let mut s = serializer.serialize_struct(NAME, 1)?;
+        s.serialize_field(FIELD, &self.to_string())?;
+        s.end()
+    }
+}
+
+impl<'de> de::Deserialize<'de> for Datetime {
+    fn deserialize<D>(deserializer: D) -> Result<Datetime, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        struct DatetimeVisitor;
+
+        impl<'de> de::Visitor<'de> for DatetimeVisitor {
+            type Value = Datetime;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                formatter.write_str("a TOML datetime")
+            }
+
+            fn visit_map<V>(self, mut visitor: V) -> Result<Datetime, V::Error>
+            where
+                V: de::MapAccess<'de>,
+            {
+                let value = visitor.next_key::<DatetimeKey>()?;
+                if value.is_none() {
+                    return Err(de::Error::custom("datetime key not found"));
+                }
+                let v: DatetimeFromString = visitor.next_value()?;
+                Ok(v.value)
+            }
+        }
+
+        static FIELDS: [&str; 1] = [FIELD];
+        deserializer.deserialize_struct(NAME, &FIELDS, DatetimeVisitor)
+    }
+}
+
+struct DatetimeKey;
+
+impl<'de> de::Deserialize<'de> for DatetimeKey {
+    fn deserialize<D>(deserializer: D) -> Result<DatetimeKey, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        struct FieldVisitor;
+
+        impl<'de> de::Visitor<'de> for FieldVisitor {
+            type Value = ();
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                formatter.write_str("a valid datetime field")
+            }
+
+            fn visit_str<E>(self, s: &str) -> Result<(), E>
+            where
+                E: de::Error,
+            {
+                if s == FIELD {
+                    Ok(())
+                } else {
+                    Err(de::Error::custom("expected field with custom name"))
+                }
+            }
+        }
+
+        deserializer.deserialize_identifier(FieldVisitor)?;
+        Ok(DatetimeKey)
+    }
+}
+
+pub struct DatetimeFromString {
+    pub value: Datetime,
+}
+
+impl<'de> de::Deserialize<'de> for DatetimeFromString {
+    fn deserialize<D>(deserializer: D) -> Result<DatetimeFromString, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        struct Visitor;
+
+        impl<'de> de::Visitor<'de> for Visitor {
+            type Value = DatetimeFromString;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                formatter.write_str("string containing a datetime")
+            }
+
+            fn visit_str<E>(self, s: &str) -> Result<DatetimeFromString, E>
+            where
+                E: de::Error,
+            {
+                match s.parse() {
+                    Ok(date) => Ok(DatetimeFromString { value: date }),
+                    Err(e) => Err(de::Error::custom(e)),
+                }
+            }
+        }
+
+        deserializer.deserialize_str(Visitor)
+    }
+}
+
+impl fmt::Display for DatetimeParseError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        "failed to parse datetime".fmt(f)
+    }
+}
+
+impl error::Error for DatetimeParseError {}
diff --git a/crates/toml/src/de.rs b/crates/toml/src/de.rs
new file mode 100644
index 00000000..fbc6009c
--- /dev/null
+++ b/crates/toml/src/de.rs
@@ -0,0 +1,2262 @@
+//! Deserializing TOML into Rust structures.
+//!
+//! This module contains all the Serde support for deserializing TOML documents
+//! into Rust structures. Note that some top-level functions here are also
+//! provided at the top of the crate.
+
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::error;
+use std::f64;
+use std::fmt;
+use std::iter;
+use std::marker::PhantomData;
+use std::str;
+use std::vec;
+
+use serde::de;
+use serde::de::value::BorrowedStrDeserializer;
+use serde::de::IntoDeserializer;
+
+use crate::datetime;
+use crate::spanned;
+use crate::tokens::{Error as TokenError, Span, Token, Tokenizer};
+
+/// Type Alias for a TOML Table pair
+type TablePair<'a> = ((Span, Cow<'a, str>), Value<'a>);
+
+/// Deserializes a byte slice into a type.
+///
+/// This function will attempt to interpret `bytes` as UTF-8 data and then
+/// deserialize `T` from the TOML document provided.
+pub fn from_slice<'de, T>(bytes: &'de [u8]) -> Result<T, Error>
+where
+    T: de::Deserialize<'de>,
+{
+    match str::from_utf8(bytes) {
+        Ok(s) => from_str(s),
+        Err(e) => Err(Error::custom(None, e.to_string())),
+    }
+}
+
+/// Deserializes a string into a type.
+///
+/// This function will attempt to interpret `s` as a TOML document and
+/// deserialize `T` from the document.
+///
+/// # Examples
+///
+/// ```
+/// use serde_derive::Deserialize;
+///
+/// #[derive(Deserialize)]
+/// struct Config {
+///     title: String,
+///     owner: Owner,
+/// }
+///
+/// #[derive(Deserialize)]
+/// struct Owner {
+///     name: String,
+/// }
+///
+/// fn main() {
+///     let config: Config = toml::from_str(r#"
+///         title = 'TOML Example'
+///
+///         [owner]
+///         name = 'Lisa'
+///     "#).unwrap();
+///
+///     assert_eq!(config.title, "TOML Example");
+///     assert_eq!(config.owner.name, "Lisa");
+/// }
+/// ```
+pub fn from_str<'de, T>(s: &'de str) -> Result<T, Error>
+where
+    T: de::Deserialize<'de>,
+{
+    let mut d = Deserializer::new(s);
+    let ret = T::deserialize(&mut d)?;
+    d.end()?;
+    Ok(ret)
+}
+
+/// Errors that can occur when deserializing a type.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct Error {
+    inner: Box<ErrorInner>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+struct ErrorInner {
+    kind: ErrorKind,
+    line: Option<usize>,
+    col: usize,
+    at: Option<usize>,
+    message: String,
+    key: Vec<String>,
+}
+
+/// Errors that can occur when deserializing a type.
+#[derive(Debug, PartialEq, Eq, Clone)]
+enum ErrorKind {
+    /// EOF was reached when looking for a value
+    UnexpectedEof,
+
+    /// An invalid character not allowed in a string was found
+    InvalidCharInString(char),
+
+    /// An invalid character was found as an escape
+    InvalidEscape(char),
+
+    /// An invalid character was found in a hex escape
+    InvalidHexEscape(char),
+
+    /// An invalid escape value was specified in a hex escape in a string.
+    ///
+    /// Valid values are in the plane of unicode codepoints.
+    InvalidEscapeValue(u32),
+
+    /// A newline in a string was encountered when one was not allowed.
+    NewlineInString,
+
+    /// An unexpected character was encountered, typically when looking for a
+    /// value.
+    Unexpected(char),
+
+    /// An unterminated string was found where EOF was found before the ending
+    /// EOF mark.
+    UnterminatedString,
+
+    /// A newline was found in a table key.
+    NewlineInTableKey,
+
+    /// A number failed to parse
+    NumberInvalid,
+
+    /// A date or datetime was invalid
+    DateInvalid,
+
+    /// Wanted one sort of token, but found another.
+    Wanted {
+        /// Expected token type
+        expected: &'static str,
+        /// Actually found token type
+        found: &'static str,
+    },
+
+    /// A duplicate table definition was found.
+    DuplicateTable(String),
+
+    /// A previously defined table was redefined as an array.
+    RedefineAsArray,
+
+    /// An empty table key was found.
+    EmptyTableKey,
+
+    /// Multiline strings are not allowed for key
+    MultilineStringKey,
+
+    /// A custom error which could be generated when deserializing a particular
+    /// type.
+    Custom,
+
+    /// A tuple with a certain number of elements was expected but something
+    /// else was found.
+    ExpectedTuple(usize),
+
+    /// Expected table keys to be in increasing tuple index order, but something
+    /// else was found.
+    ExpectedTupleIndex {
+        /// Expected index.
+        expected: usize,
+        /// Key that was specified.
+        found: String,
+    },
+
+    /// An empty table was expected but entries were found
+    ExpectedEmptyTable,
+
+    /// Dotted key attempted to extend something that is not a table.
+    DottedKeyInvalidType,
+
+    /// An unexpected key was encountered.
+    ///
+    /// Used when deserializing a struct with a limited set of fields.
+    UnexpectedKeys {
+        /// The unexpected keys.
+        keys: Vec<String>,
+        /// Keys that may be specified.
+        available: &'static [&'static str],
+    },
+
+    /// Unquoted string was found when quoted one was expected
+    UnquotedString,
+
+    #[doc(hidden)]
+    __Nonexhaustive,
+}
+
+/// Deserialization implementation for TOML.
+pub struct Deserializer<'a> {
+    require_newline_after_table: bool,
+    allow_duplciate_after_longer_table: bool,
+    input: &'a str,
+    tokens: Tokenizer<'a>,
+}
+
+impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        let mut tables = self.tables()?;
+        let table_indices = build_table_indices(&tables);
+        let table_pindices = build_table_pindices(&tables);
+
+        let res = visitor.visit_map(MapVisitor {
+            values: Vec::new().into_iter().peekable(),
+            next_value: None,
+            depth: 0,
+            cur: 0,
+            cur_parent: 0,
+            max: tables.len(),
+            table_indices: &table_indices,
+            table_pindices: &table_pindices,
+            tables: &mut tables,
+            array: false,
+            de: self,
+        });
+        res.map_err(|mut err| {
+            // Errors originating from this library (toml), have an offset
+            // attached to them already. Other errors, like those originating
+            // from serde (like "missing field") or from a custom deserializer,
+            // do not have offsets on them. Here, we do a best guess at their
+            // location, by attributing them to the "current table" (the last
+            // item in `tables`).
+            err.fix_offset(|| tables.last().map(|table| table.at));
+            err.fix_linecol(|at| self.to_linecol(at));
+            err
+        })
+    }
+
+    // Called when the type to deserialize is an enum, as opposed to a field in the type.
+    fn deserialize_enum<V>(
+        self,
+        _name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        let (value, name) = self.string_or_table()?;
+        match value.e {
+            E::String(val) => visitor.visit_enum(val.into_deserializer()),
+            E::InlineTable(values) => {
+                if values.len() != 1 {
+                    Err(Error::from_kind(
+                        Some(value.start),
+                        ErrorKind::Wanted {
+                            expected: "exactly 1 element",
+                            found: if values.is_empty() {
+                                "zero elements"
+                            } else {
+                                "more than 1 element"
+                            },
+                        },
+                    ))
+                } else {
+                    visitor.visit_enum(InlineTableDeserializer {
+                        values: values.into_iter(),
+                        next_value: None,
+                    })
+                }
+            }
+            E::DottedTable(_) => visitor.visit_enum(DottedTableDeserializer {
+                name: name.expect("Expected table header to be passed."),
+                value,
+            }),
+            e => Err(Error::from_kind(
+                Some(value.start),
+                ErrorKind::Wanted {
+                    expected: "string or table",
+                    found: e.type_name(),
+                },
+            )),
+        }
+    }
+
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if name == spanned::NAME && fields == [spanned::START, spanned::END, spanned::VALUE] {
+            let start = 0;
+            let end = self.input.len();
+
+            let res = visitor.visit_map(SpannedDeserializer {
+                phantom_data: PhantomData,
+                start: Some(start),
+                value: Some(self),
+                end: Some(end),
+            });
+            return res;
+        }
+
+        self.deserialize_any(visitor)
+    }
+
+    serde::forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
+        bytes byte_buf map unit newtype_struct
+        ignored_any unit_struct tuple_struct tuple option identifier
+    }
+}
+
+// Builds a datastructure that allows for efficient sublinear lookups.
+// The returned HashMap contains a mapping from table header (like [a.b.c])
+// to list of tables with that precise name. The tables are being identified
+// by their index in the passed slice. We use a list as the implementation
+// uses this data structure for arrays as well as tables,
+// so if any top level [[name]] array contains multiple entries,
+// there are multiple entries in the list.
+// The lookup is performed in the `SeqAccess` implementation of `MapVisitor`.
+// The lists are ordered, which we exploit in the search code by using
+// bisection.
+fn build_table_indices<'de>(tables: &[Table<'de>]) -> HashMap<Vec<Cow<'de, str>>, Vec<usize>> {
+    let mut res = HashMap::new();
+    for (i, table) in tables.iter().enumerate() {
+        let header = table.header.iter().map(|v| v.1.clone()).collect::<Vec<_>>();
+        res.entry(header).or_insert_with(Vec::new).push(i);
+    }
+    res
+}
+
+// Builds a datastructure that allows for efficient sublinear lookups.
+// The returned HashMap contains a mapping from table header (like [a.b.c])
+// to list of tables whose name at least starts with the specified
+// name. So searching for [a.b] would give both [a.b.c.d] as well as [a.b.e].
+// The tables are being identified by their index in the passed slice.
+//
+// A list is used for two reasons: First, the implementation also
+// stores arrays in the same data structure and any top level array
+// of size 2 or greater creates multiple entries in the list with the
+// same shared name. Second, there can be multiple tables sharing
+// the same prefix.
+//
+// The lookup is performed in the `MapAccess` implementation of `MapVisitor`.
+// The lists are ordered, which we exploit in the search code by using
+// bisection.
+fn build_table_pindices<'de>(tables: &[Table<'de>]) -> HashMap<Vec<Cow<'de, str>>, Vec<usize>> {
+    let mut res = HashMap::new();
+    for (i, table) in tables.iter().enumerate() {
+        let header = table.header.iter().map(|v| v.1.clone()).collect::<Vec<_>>();
+        for len in 0..=header.len() {
+            res.entry(header[..len].to_owned())
+                .or_insert_with(Vec::new)
+                .push(i);
+        }
+    }
+    res
+}
+
+fn headers_equal<'a, 'b>(hdr_a: &[(Span, Cow<'a, str>)], hdr_b: &[(Span, Cow<'b, str>)]) -> bool {
+    if hdr_a.len() != hdr_b.len() {
+        return false;
+    }
+    hdr_a.iter().zip(hdr_b.iter()).all(|(h1, h2)| h1.1 == h2.1)
+}
+
+struct Table<'a> {
+    at: usize,
+    header: Vec<(Span, Cow<'a, str>)>,
+    values: Option<Vec<TablePair<'a>>>,
+    array: bool,
+}
+
+struct MapVisitor<'de, 'b> {
+    values: iter::Peekable<vec::IntoIter<TablePair<'de>>>,
+    next_value: Option<TablePair<'de>>,
+    depth: usize,
+    cur: usize,
+    cur_parent: usize,
+    max: usize,
+    table_indices: &'b HashMap<Vec<Cow<'de, str>>, Vec<usize>>,
+    table_pindices: &'b HashMap<Vec<Cow<'de, str>>, Vec<usize>>,
+    tables: &'b mut [Table<'de>],
+    array: bool,
+    de: &'b mut Deserializer<'de>,
+}
+
+impl<'de, 'b> de::MapAccess<'de> for MapVisitor<'de, 'b> {
+    type Error = Error;
+
+    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
+    where
+        K: de::DeserializeSeed<'de>,
+    {
+        if self.cur_parent == self.max || self.cur == self.max {
+            return Ok(None);
+        }
+
+        loop {
+            assert!(self.next_value.is_none());
+            if let Some((key, value)) = self.values.next() {
+                let ret = seed.deserialize(StrDeserializer::spanned(key.clone()))?;
+                self.next_value = Some((key, value));
+                return Ok(Some(ret));
+            }
+
+            let next_table = {
+                let prefix_stripped = self.tables[self.cur_parent].header[..self.depth]
+                    .iter()
+                    .map(|v| v.1.clone())
+                    .collect::<Vec<_>>();
+                self.table_pindices
+                    .get(&prefix_stripped)
+                    .and_then(|entries| {
+                        let start = entries.binary_search(&self.cur).unwrap_or_else(|v| v);
+                        if start == entries.len() || entries[start] < self.cur {
+                            return None;
+                        }
+                        entries[start..]
+                            .iter()
+                            .filter_map(|i| if *i < self.max { Some(*i) } else { None })
+                            .map(|i| (i, &self.tables[i]))
+                            .find(|(_, table)| table.values.is_some())
+                            .map(|p| p.0)
+                    })
+            };
+
+            let pos = match next_table {
+                Some(pos) => pos,
+                None => return Ok(None),
+            };
+            self.cur = pos;
+
+            // Test to see if we're duplicating our parent's table, and if so
+            // then this is an error in the toml format
+            if self.cur_parent != pos {
+                if headers_equal(
+                    &self.tables[self.cur_parent].header,
+                    &self.tables[pos].header,
+                ) {
+                    let at = self.tables[pos].at;
+                    let name = self.tables[pos]
+                        .header
+                        .iter()
+                        .map(|k| k.1.to_owned())
+                        .collect::<Vec<_>>()
+                        .join(".");
+                    return Err(self.de.error(at, ErrorKind::DuplicateTable(name)));
+                }
+
+                // If we're here we know we should share the same prefix, and if
+                // the longer table was defined first then we want to narrow
+                // down our parent's length if possible to ensure that we catch
+                // duplicate tables defined afterwards.
+                if !self.de.allow_duplciate_after_longer_table {
+                    let parent_len = self.tables[self.cur_parent].header.len();
+                    let cur_len = self.tables[pos].header.len();
+                    if cur_len < parent_len {
+                        self.cur_parent = pos;
+                    }
+                }
+            }
+
+            let table = &mut self.tables[pos];
+
+            // If we're not yet at the appropriate depth for this table then we
+            // just next the next portion of its header and then continue
+            // decoding.
+            if self.depth != table.header.len() {
+                let key = &table.header[self.depth];
+                let key = seed.deserialize(StrDeserializer::spanned(key.clone()))?;
+                return Ok(Some(key));
+            }
+
+            // Rule out cases like:
+            //
+            //      [[foo.bar]]
+            //      [[foo]]
+            if table.array {
+                let kind = ErrorKind::RedefineAsArray;
+                return Err(self.de.error(table.at, kind));
+            }
+
+            self.values = table
+                .values
+                .take()
+                .expect("Unable to read table values")
+                .into_iter()
+                .peekable();
+        }
+    }
+
+    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        if let Some((k, v)) = self.next_value.take() {
+            match seed.deserialize(ValueDeserializer::new(v)) {
+                Ok(v) => return Ok(v),
+                Err(mut e) => {
+                    e.add_key_context(&k.1);
+                    return Err(e);
+                }
+            }
+        }
+
+        let array =
+            self.tables[self.cur].array && self.depth == self.tables[self.cur].header.len() - 1;
+        self.cur += 1;
+        let res = seed.deserialize(MapVisitor {
+            values: Vec::new().into_iter().peekable(),
+            next_value: None,
+            depth: self.depth + if array { 0 } else { 1 },
+            cur_parent: self.cur - 1,
+            cur: 0,
+            max: self.max,
+            array,
+            table_indices: &*self.table_indices,
+            table_pindices: &*self.table_pindices,
+            tables: &mut *self.tables,
+            de: &mut *self.de,
+        });
+        res.map_err(|mut e| {
+            e.add_key_context(&self.tables[self.cur - 1].header[self.depth].1);
+            e
+        })
+    }
+}
+
+impl<'de, 'b> de::SeqAccess<'de> for MapVisitor<'de, 'b> {
+    type Error = Error;
+
+    fn next_element_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
+    where
+        K: de::DeserializeSeed<'de>,
+    {
+        assert!(self.next_value.is_none());
+        assert!(self.values.next().is_none());
+
+        if self.cur_parent == self.max {
+            return Ok(None);
+        }
+
+        let header_stripped = self.tables[self.cur_parent]
+            .header
+            .iter()
+            .map(|v| v.1.clone())
+            .collect::<Vec<_>>();
+        let start_idx = self.cur_parent + 1;
+        let next = self
+            .table_indices
+            .get(&header_stripped)
+            .and_then(|entries| {
+                let start = entries.binary_search(&start_idx).unwrap_or_else(|v| v);
+                if start == entries.len() || entries[start] < start_idx {
+                    return None;
+                }
+                entries[start..]
+                    .iter()
+                    .filter_map(|i| if *i < self.max { Some(*i) } else { None })
+                    .map(|i| (i, &self.tables[i]))
+                    .find(|(_, table)| table.array)
+                    .map(|p| p.0)
+            })
+            .unwrap_or(self.max);
+
+        let ret = seed.deserialize(MapVisitor {
+            values: self.tables[self.cur_parent]
+                .values
+                .take()
+                .expect("Unable to read table values")
+                .into_iter()
+                .peekable(),
+            next_value: None,
+            depth: self.depth + 1,
+            cur_parent: self.cur_parent,
+            max: next,
+            cur: 0,
+            array: false,
+            table_indices: &*self.table_indices,
+            table_pindices: &*self.table_pindices,
+            tables: &mut self.tables,
+            de: &mut self.de,
+        })?;
+        self.cur_parent = next;
+        Ok(Some(ret))
+    }
+}
+
+impl<'de, 'b> de::Deserializer<'de> for MapVisitor<'de, 'b> {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if self.array {
+            visitor.visit_seq(self)
+        } else {
+            visitor.visit_map(self)
+        }
+    }
+
+    // `None` is interpreted as a missing field so be sure to implement `Some`
+    // as a present field.
+    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_some(self)
+    }
+
+    fn deserialize_newtype_struct<V>(
+        self,
+        _name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_newtype_struct(self)
+    }
+
+    fn deserialize_struct<V>(
+        mut self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if name == spanned::NAME
+            && fields == [spanned::START, spanned::END, spanned::VALUE]
+            && !(self.array && !self.values.peek().is_none())
+        {
+            // TODO we can't actually emit spans here for the *entire* table/array
+            // due to the format that toml uses. Setting the start and end to 0 is
+            // *detectable* (and no reasonable span would look like that),
+            // it would be better to expose this in the API via proper
+            // ADTs like Option<T>.
+            let start = 0;
+            let end = 0;
+
+            let res = visitor.visit_map(SpannedDeserializer {
+                phantom_data: PhantomData,
+                start: Some(start),
+                value: Some(self),
+                end: Some(end),
+            });
+            return res;
+        }
+
+        self.deserialize_any(visitor)
+    }
+
+    fn deserialize_enum<V>(
+        self,
+        _name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if self.tables.len() != 1 {
+            return Err(Error::custom(
+                Some(self.cur),
+                "enum table must contain exactly one table".into(),
+            ));
+        }
+        let table = &mut self.tables[0];
+        let values = table.values.take().expect("table has no values?");
+        if table.header.is_empty() {
+            return Err(self.de.error(self.cur, ErrorKind::EmptyTableKey));
+        }
+        let name = table.header[table.header.len() - 1].1.to_owned();
+        visitor.visit_enum(DottedTableDeserializer {
+            name,
+            value: Value {
+                e: E::DottedTable(values),
+                start: 0,
+                end: 0,
+            },
+        })
+    }
+
+    serde::forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
+        bytes byte_buf map unit identifier
+        ignored_any unit_struct tuple_struct tuple
+    }
+}
+
+struct StrDeserializer<'a> {
+    span: Option<Span>,
+    key: Cow<'a, str>,
+}
+
+impl<'a> StrDeserializer<'a> {
+    fn spanned(inner: (Span, Cow<'a, str>)) -> StrDeserializer<'a> {
+        StrDeserializer {
+            span: Some(inner.0),
+            key: inner.1,
+        }
+    }
+    fn new(key: Cow<'a, str>) -> StrDeserializer<'a> {
+        StrDeserializer { span: None, key }
+    }
+}
+
+impl<'a, 'b> de::IntoDeserializer<'a, Error> for StrDeserializer<'a> {
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        self
+    }
+}
+
+impl<'de> de::Deserializer<'de> for StrDeserializer<'de> {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self.key {
+            Cow::Borrowed(s) => visitor.visit_borrowed_str(s),
+            Cow::Owned(s) => visitor.visit_string(s),
+        }
+    }
+
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if name == spanned::NAME && fields == [spanned::START, spanned::END, spanned::VALUE] {
+            if let Some(span) = self.span {
+                return visitor.visit_map(SpannedDeserializer {
+                    phantom_data: PhantomData,
+                    start: Some(span.start),
+                    value: Some(StrDeserializer::new(self.key)),
+                    end: Some(span.end),
+                });
+            }
+        }
+        self.deserialize_any(visitor)
+    }
+
+    serde::forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
+        bytes byte_buf map option unit newtype_struct
+        ignored_any unit_struct tuple_struct tuple enum identifier
+    }
+}
+
+struct ValueDeserializer<'a> {
+    value: Value<'a>,
+    validate_struct_keys: bool,
+}
+
+impl<'a> ValueDeserializer<'a> {
+    fn new(value: Value<'a>) -> ValueDeserializer<'a> {
+        ValueDeserializer {
+            value,
+            validate_struct_keys: false,
+        }
+    }
+
+    fn with_struct_key_validation(mut self) -> Self {
+        self.validate_struct_keys = true;
+        self
+    }
+}
+
+impl<'de> de::Deserializer<'de> for ValueDeserializer<'de> {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        let start = self.value.start;
+        let res = match self.value.e {
+            E::Integer(i) => visitor.visit_i64(i),
+            E::Boolean(b) => visitor.visit_bool(b),
+            E::Float(f) => visitor.visit_f64(f),
+            E::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
+            E::String(Cow::Owned(s)) => visitor.visit_string(s),
+            E::Datetime(s) => visitor.visit_map(DatetimeDeserializer {
+                date: s,
+                visited: false,
+            }),
+            E::Array(values) => {
+                let mut s = de::value::SeqDeserializer::new(values.into_iter());
+                let ret = visitor.visit_seq(&mut s)?;
+                s.end()?;
+                Ok(ret)
+            }
+            E::InlineTable(values) | E::DottedTable(values) => {
+                visitor.visit_map(InlineTableDeserializer {
+                    values: values.into_iter(),
+                    next_value: None,
+                })
+            }
+        };
+        res.map_err(|mut err| {
+            // Attribute the error to whatever value returned the error.
+            err.fix_offset(|| Some(start));
+            err
+        })
+    }
+
+    fn deserialize_struct<V>(
+        self,
+        name: &'static str,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        if name == datetime::NAME && fields == [datetime::FIELD] {
+            if let E::Datetime(s) = self.value.e {
+                return visitor.visit_map(DatetimeDeserializer {
+                    date: s,
+                    visited: false,
+                });
+            }
+        }
+
+        if self.validate_struct_keys {
+            match self.value.e {
+                E::InlineTable(ref values) | E::DottedTable(ref values) => {
+                    let extra_fields = values
+                        .iter()
+                        .filter_map(|key_value| {
+                            let (ref key, ref _val) = *key_value;
+                            if !fields.contains(&&*(key.1)) {
+                                Some(key.clone())
+                            } else {
+                                None
+                            }
+                        })
+                        .collect::<Vec<_>>();
+
+                    if !extra_fields.is_empty() {
+                        return Err(Error::from_kind(
+                            Some(self.value.start),
+                            ErrorKind::UnexpectedKeys {
+                                keys: extra_fields
+                                    .iter()
+                                    .map(|k| k.1.to_string())
+                                    .collect::<Vec<_>>(),
+                                available: fields,
+                            },
+                        ));
+                    }
+                }
+                _ => {}
+            }
+        }
+
+        if name == spanned::NAME && fields == [spanned::START, spanned::END, spanned::VALUE] {
+            let start = self.value.start;
+            let end = self.value.end;
+
+            return visitor.visit_map(SpannedDeserializer {
+                phantom_data: PhantomData,
+                start: Some(start),
+                value: Some(self.value),
+                end: Some(end),
+            });
+        }
+
+        self.deserialize_any(visitor)
+    }
+
+    // `None` is interpreted as a missing field so be sure to implement `Some`
+    // as a present field.
+    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_some(self)
+    }
+
+    fn deserialize_enum<V>(
+        self,
+        _name: &'static str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self.value.e {
+            E::String(val) => visitor.visit_enum(val.into_deserializer()),
+            E::InlineTable(values) => {
+                if values.len() != 1 {
+                    Err(Error::from_kind(
+                        Some(self.value.start),
+                        ErrorKind::Wanted {
+                            expected: "exactly 1 element",
+                            found: if values.is_empty() {
+                                "zero elements"
+                            } else {
+                                "more than 1 element"
+                            },
+                        },
+                    ))
+                } else {
+                    visitor.visit_enum(InlineTableDeserializer {
+                        values: values.into_iter(),
+                        next_value: None,
+                    })
+                }
+            }
+            e => Err(Error::from_kind(
+                Some(self.value.start),
+                ErrorKind::Wanted {
+                    expected: "string or inline table",
+                    found: e.type_name(),
+                },
+            )),
+        }
+    }
+
+    fn deserialize_newtype_struct<V>(
+        self,
+        _name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_newtype_struct(self)
+    }
+
+    serde::forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
+        bytes byte_buf map unit identifier
+        ignored_any unit_struct tuple_struct tuple
+    }
+}
+
+impl<'de, 'b> de::IntoDeserializer<'de, Error> for MapVisitor<'de, 'b> {
+    type Deserializer = MapVisitor<'de, 'b>;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        self
+    }
+}
+
+impl<'de, 'b> de::IntoDeserializer<'de, Error> for &'b mut Deserializer<'de> {
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        self
+    }
+}
+
+impl<'de> de::IntoDeserializer<'de, Error> for Value<'de> {
+    type Deserializer = ValueDeserializer<'de>;
+
+    fn into_deserializer(self) -> Self::Deserializer {
+        ValueDeserializer::new(self)
+    }
+}
+
+struct SpannedDeserializer<'de, T: de::IntoDeserializer<'de, Error>> {
+    phantom_data: PhantomData<&'de ()>,
+    start: Option<usize>,
+    end: Option<usize>,
+    value: Option<T>,
+}
+
+impl<'de, T> de::MapAccess<'de> for SpannedDeserializer<'de, T>
+where
+    T: de::IntoDeserializer<'de, Error>,
+{
+    type Error = Error;
+
+    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
+    where
+        K: de::DeserializeSeed<'de>,
+    {
+        if self.start.is_some() {
+            seed.deserialize(BorrowedStrDeserializer::new(spanned::START))
+                .map(Some)
+        } else if self.end.is_some() {
+            seed.deserialize(BorrowedStrDeserializer::new(spanned::END))
+                .map(Some)
+        } else if self.value.is_some() {
+            seed.deserialize(BorrowedStrDeserializer::new(spanned::VALUE))
+                .map(Some)
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        if let Some(start) = self.start.take() {
+            seed.deserialize(start.into_deserializer())
+        } else if let Some(end) = self.end.take() {
+            seed.deserialize(end.into_deserializer())
+        } else if let Some(value) = self.value.take() {
+            seed.deserialize(value.into_deserializer())
+        } else {
+            panic!("next_value_seed called before next_key_seed")
+        }
+    }
+}
+
+struct DatetimeDeserializer<'a> {
+    visited: bool,
+    date: &'a str,
+}
+
+impl<'de> de::MapAccess<'de> for DatetimeDeserializer<'de> {
+    type Error = Error;
+
+    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
+    where
+        K: de::DeserializeSeed<'de>,
+    {
+        if self.visited {
+            return Ok(None);
+        }
+        self.visited = true;
+        seed.deserialize(DatetimeFieldDeserializer).map(Some)
+    }
+
+    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        seed.deserialize(StrDeserializer::new(self.date.into()))
+    }
+}
+
+struct DatetimeFieldDeserializer;
+
+impl<'de> de::Deserializer<'de> for DatetimeFieldDeserializer {
+    type Error = Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_borrowed_str(datetime::FIELD)
+    }
+
+    serde::forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
+        bytes byte_buf map struct option unit newtype_struct
+        ignored_any unit_struct tuple_struct tuple enum identifier
+    }
+}
+
+struct DottedTableDeserializer<'a> {
+    name: Cow<'a, str>,
+    value: Value<'a>,
+}
+
+impl<'de> de::EnumAccess<'de> for DottedTableDeserializer<'de> {
+    type Error = Error;
+    type Variant = TableEnumDeserializer<'de>;
+
+    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        let (name, value) = (self.name, self.value);
+        seed.deserialize(StrDeserializer::new(name))
+            .map(|val| (val, TableEnumDeserializer { value }))
+    }
+}
+
+struct InlineTableDeserializer<'a> {
+    values: vec::IntoIter<TablePair<'a>>,
+    next_value: Option<Value<'a>>,
+}
+
+impl<'de> de::MapAccess<'de> for InlineTableDeserializer<'de> {
+    type Error = Error;
+
+    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
+    where
+        K: de::DeserializeSeed<'de>,
+    {
+        let (key, value) = match self.values.next() {
+            Some(pair) => pair,
+            None => return Ok(None),
+        };
+        self.next_value = Some(value);
+        seed.deserialize(StrDeserializer::spanned(key)).map(Some)
+    }
+
+    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        let value = self.next_value.take().expect("Unable to read table values");
+        seed.deserialize(ValueDeserializer::new(value))
+    }
+}
+
+impl<'de> de::EnumAccess<'de> for InlineTableDeserializer<'de> {
+    type Error = Error;
+    type Variant = TableEnumDeserializer<'de>;
+
+    fn variant_seed<V>(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error>
+    where
+        V: de::DeserializeSeed<'de>,
+    {
+        let (key, value) = match self.values.next() {
+            Some(pair) => pair,
+            None => {
+                return Err(Error::from_kind(
+                    None, // FIXME: How do we get an offset here?
+                    ErrorKind::Wanted {
+                        expected: "table with exactly 1 entry",
+                        found: "empty table",
+                    },
+                ));
+            }
+        };
+
+        seed.deserialize(StrDeserializer::new(key.1))
+            .map(|val| (val, TableEnumDeserializer { value }))
+    }
+}
+
+/// Deserializes table values into enum variants.
+struct TableEnumDeserializer<'a> {
+    value: Value<'a>,
+}
+
+impl<'de> de::VariantAccess<'de> for TableEnumDeserializer<'de> {
+    type Error = Error;
+
+    fn unit_variant(self) -> Result<(), Self::Error> {
+        match self.value.e {
+            E::InlineTable(values) | E::DottedTable(values) => {
+                if values.is_empty() {
+                    Ok(())
+                } else {
+                    Err(Error::from_kind(
+                        Some(self.value.start),
+                        ErrorKind::ExpectedEmptyTable,
+                    ))
+                }
+            }
+            e => Err(Error::from_kind(
+                Some(self.value.start),
+                ErrorKind::Wanted {
+                    expected: "table",
+                    found: e.type_name(),
+                },
+            )),
+        }
+    }
+
+    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Self::Error>
+    where
+        T: de::DeserializeSeed<'de>,
+    {
+        seed.deserialize(ValueDeserializer::new(self.value))
+    }
+
+    fn tuple_variant<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self.value.e {
+            E::InlineTable(values) | E::DottedTable(values) => {
+                let tuple_values = values
+                    .into_iter()
+                    .enumerate()
+                    .map(|(index, (key, value))| match key.1.parse::<usize>() {
+                        Ok(key_index) if key_index == index => Ok(value),
+                        Ok(_) | Err(_) => Err(Error::from_kind(
+                            Some(key.0.start),
+                            ErrorKind::ExpectedTupleIndex {
+                                expected: index,
+                                found: key.1.to_string(),
+                            },
+                        )),
+                    })
+                    // Fold all values into a `Vec`, or return the first error.
+                    .fold(Ok(Vec::with_capacity(len)), |result, value_result| {
+                        result.and_then(move |mut tuple_values| match value_result {
+                            Ok(value) => {
+                                tuple_values.push(value);
+                                Ok(tuple_values)
+                            }
+                            // `Result<de::Value, Self::Error>` to `Result<Vec<_>, Self::Error>`
+                            Err(e) => Err(e),
+                        })
+                    })?;
+
+                if tuple_values.len() == len {
+                    de::Deserializer::deserialize_seq(
+                        ValueDeserializer::new(Value {
+                            e: E::Array(tuple_values),
+                            start: self.value.start,
+                            end: self.value.end,
+                        }),
+                        visitor,
+                    )
+                } else {
+                    Err(Error::from_kind(
+                        Some(self.value.start),
+                        ErrorKind::ExpectedTuple(len),
+                    ))
+                }
+            }
+            e => Err(Error::from_kind(
+                Some(self.value.start),
+                ErrorKind::Wanted {
+                    expected: "table",
+                    found: e.type_name(),
+                },
+            )),
+        }
+    }
+
+    fn struct_variant<V>(
+        self,
+        fields: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, Self::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        de::Deserializer::deserialize_struct(
+            ValueDeserializer::new(self.value).with_struct_key_validation(),
+            "", // TODO: this should be the variant name
+            fields,
+            visitor,
+        )
+    }
+}
+
+impl<'a> Deserializer<'a> {
+    /// Creates a new deserializer which will be deserializing the string
+    /// provided.
+    pub fn new(input: &'a str) -> Deserializer<'a> {
+        Deserializer {
+            tokens: Tokenizer::new(input),
+            input,
+            require_newline_after_table: true,
+            allow_duplciate_after_longer_table: false,
+        }
+    }
+
+    /// The `Deserializer::end` method should be called after a value has been
+    /// fully deserialized.  This allows the `Deserializer` to validate that the
+    /// input stream is at the end or that it only has trailing
+    /// whitespace/comments.
+    pub fn end(&mut self) -> Result<(), Error> {
+        Ok(())
+    }
+
+    /// Historical versions of toml-rs accidentally allowed a newline after a
+    /// table definition, but the TOML spec requires a newline after a table
+    /// definition header.
+    ///
+    /// This option can be set to `false` (the default is `true`) to emulate
+    /// this behavior for backwards compatibility with older toml-rs versions.
+    pub fn set_require_newline_after_table(&mut self, require: bool) {
+        self.require_newline_after_table = require;
+    }
+
+    /// Historical versions of toml-rs accidentally allowed a duplicate table
+    /// header after a longer table header was previously defined. This is
+    /// invalid according to the TOML spec, however.
+    ///
+    /// This option can be set to `true` (the default is `false`) to emulate
+    /// this behavior for backwards compatibility with older toml-rs versions.
+    pub fn set_allow_duplicate_after_longer_table(&mut self, allow: bool) {
+        self.allow_duplciate_after_longer_table = allow;
+    }
+
+    fn tables(&mut self) -> Result<Vec<Table<'a>>, Error> {
+        let mut tables = Vec::new();
+        let mut cur_table = Table {
+            at: 0,
+            header: Vec::new(),
+            values: None,
+            array: false,
+        };
+
+        while let Some(line) = self.line()? {
+            match line {
+                Line::Table {
+                    at,
+                    mut header,
+                    array,
+                } => {
+                    if !cur_table.header.is_empty() || cur_table.values.is_some() {
+                        tables.push(cur_table);
+                    }
+                    cur_table = Table {
+                        at,
+                        header: Vec::new(),
+                        values: Some(Vec::new()),
+                        array,
+                    };
+                    loop {
+                        let part = header.next().map_err(|e| self.token_error(e));
+                        match part? {
+                            Some(part) => cur_table.header.push(part),
+                            None => break,
+                        }
+                    }
+                }
+                Line::KeyValue(key, value) => {
+                    if cur_table.values.is_none() {
+                        cur_table.values = Some(Vec::new());
+                    }
+                    self.add_dotted_key(key, value, cur_table.values.as_mut().unwrap())?;
+                }
+            }
+        }
+        if !cur_table.header.is_empty() || cur_table.values.is_some() {
+            tables.push(cur_table);
+        }
+        Ok(tables)
+    }
+
+    fn line(&mut self) -> Result<Option<Line<'a>>, Error> {
+        loop {
+            self.eat_whitespace()?;
+            if self.eat_comment()? {
+                continue;
+            }
+            if self.eat(Token::Newline)? {
+                continue;
+            }
+            break;
+        }
+
+        match self.peek()? {
+            Some((_, Token::LeftBracket)) => self.table_header().map(Some),
+            Some(_) => self.key_value().map(Some),
+            None => Ok(None),
+        }
+    }
+
+    fn table_header(&mut self) -> Result<Line<'a>, Error> {
+        let start = self.tokens.current();
+        self.expect(Token::LeftBracket)?;
+        let array = self.eat(Token::LeftBracket)?;
+        let ret = Header::new(self.tokens.clone(), array, self.require_newline_after_table);
+        if self.require_newline_after_table {
+            self.tokens.skip_to_newline();
+        } else {
+            loop {
+                match self.next()? {
+                    Some((_, Token::RightBracket)) => {
+                        if array {
+                            self.eat(Token::RightBracket)?;
+                        }
+                        break;
+                    }
+                    Some((_, Token::Newline)) | None => break,
+                    _ => {}
+                }
+            }
+            self.eat_whitespace()?;
+        }
+        Ok(Line::Table {
+            at: start,
+            header: ret,
+            array,
+        })
+    }
+
+    fn key_value(&mut self) -> Result<Line<'a>, Error> {
+        let key = self.dotted_key()?;
+        self.eat_whitespace()?;
+        self.expect(Token::Equals)?;
+        self.eat_whitespace()?;
+
+        let value = self.value()?;
+        self.eat_whitespace()?;
+        if !self.eat_comment()? {
+            self.eat_newline_or_eof()?;
+        }
+
+        Ok(Line::KeyValue(key, value))
+    }
+
+    fn value(&mut self) -> Result<Value<'a>, Error> {
+        let at = self.tokens.current();
+        let value = match self.next()? {
+            Some((Span { start, end }, Token::String { val, .. })) => Value {
+                e: E::String(val),
+                start,
+                end,
+            },
+            Some((Span { start, end }, Token::Keylike("true"))) => Value {
+                e: E::Boolean(true),
+                start,
+                end,
+            },
+            Some((Span { start, end }, Token::Keylike("false"))) => Value {
+                e: E::Boolean(false),
+                start,
+                end,
+            },
+            Some((span, Token::Keylike(key))) => self.parse_keylike(at, span, key)?,
+            Some((span, Token::Plus)) => self.number_leading_plus(span)?,
+            Some((Span { start, .. }, Token::LeftBrace)) => {
+                self.inline_table().map(|(Span { end, .. }, table)| Value {
+                    e: E::InlineTable(table),
+                    start,
+                    end,
+                })?
+            }
+            Some((Span { start, .. }, Token::LeftBracket)) => {
+                self.array().map(|(Span { end, .. }, array)| Value {
+                    e: E::Array(array),
+                    start,
+                    end,
+                })?
+            }
+            Some(token) => {
+                return Err(self.error(
+                    at,
+                    ErrorKind::Wanted {
+                        expected: "a value",
+                        found: token.1.describe(),
+                    },
+                ));
+            }
+            None => return Err(self.eof()),
+        };
+        Ok(value)
+    }
+
+    fn parse_keylike(&mut self, at: usize, span: Span, key: &'a str) -> Result<Value<'a>, Error> {
+        if key == "inf" || key == "nan" {
+            return self.number_or_date(span, key);
+        }
+
+        let first_char = key.chars().next().expect("key should not be empty here");
+        match first_char {
+            '-' | '0'..='9' => self.number_or_date(span, key),
+            _ => Err(self.error(at, ErrorKind::UnquotedString)),
+        }
+    }
+
+    fn number_or_date(&mut self, span: Span, s: &'a str) -> Result<Value<'a>, Error> {
+        if s.contains('T')
+            || s.contains('t')
+            || (s.len() > 1 && s[1..].contains('-') && !s.contains("e-") && !s.contains("E-"))
+        {
+            self.datetime(span, s, false)
+                .map(|(Span { start, end }, d)| Value {
+                    e: E::Datetime(d),
+                    start,
+                    end,
+                })
+        } else if self.eat(Token::Colon)? {
+            self.datetime(span, s, true)
+                .map(|(Span { start, end }, d)| Value {
+                    e: E::Datetime(d),
+                    start,
+                    end,
+                })
+        } else {
+            self.number(span, s)
+        }
+    }
+
+    /// Returns a string or table value type.
+    ///
+    /// Used to deserialize enums. Unit enums may be represented as a string or a table, all other
+    /// structures (tuple, newtype, struct) must be represented as a table.
+    fn string_or_table(&mut self) -> Result<(Value<'a>, Option<Cow<'a, str>>), Error> {
+        match self.peek()? {
+            Some((span, Token::LeftBracket)) => {
+                let tables = self.tables()?;
+                if tables.len() != 1 {
+                    return Err(Error::from_kind(
+                        Some(span.start),
+                        ErrorKind::Wanted {
+                            expected: "exactly 1 table",
+                            found: if tables.is_empty() {
+                                "zero tables"
+                            } else {
+                                "more than 1 table"
+                            },
+                        },
+                    ));
+                }
+
+                let table = tables
+                    .into_iter()
+                    .next()
+                    .expect("Expected exactly one table");
+                let header = table
+                    .header
+                    .last()
+                    .expect("Expected at least one header value for table.");
+
+                let start = table.at;
+                let end = table
+                    .values
+                    .as_ref()
+                    .and_then(|values| values.last())
+                    .map(|&(_, ref val)| val.end)
+                    .unwrap_or_else(|| header.1.len());
+                Ok((
+                    Value {
+                        e: E::DottedTable(table.values.unwrap_or_else(Vec::new)),
+                        start,
+                        end,
+                    },
+                    Some(header.1.clone()),
+                ))
+            }
+            Some(_) => self.value().map(|val| (val, None)),
+            None => Err(self.eof()),
+        }
+    }
+
+    fn number(&mut self, Span { start, end }: Span, s: &'a str) -> Result<Value<'a>, Error> {
+        let to_integer = |f| Value {
+            e: E::Integer(f),
+            start,
+            end,
+        };
+        if s.starts_with("0x") {
+            self.integer(&s[2..], 16).map(to_integer)
+        } else if s.starts_with("0o") {
+            self.integer(&s[2..], 8).map(to_integer)
+        } else if s.starts_with("0b") {
+            self.integer(&s[2..], 2).map(to_integer)
+        } else if s.contains('e') || s.contains('E') {
+            self.float(s, None).map(|f| Value {
+                e: E::Float(f),
+                start,
+                end,
+            })
+        } else if self.eat(Token::Period)? {
+            let at = self.tokens.current();
+            match self.next()? {
+                Some((Span { start, end }, Token::Keylike(after))) => {
+                    self.float(s, Some(after)).map(|f| Value {
+                        e: E::Float(f),
+                        start,
+                        end,
+                    })
+                }
+                _ => Err(self.error(at, ErrorKind::NumberInvalid)),
+            }
+        } else if s == "inf" {
+            Ok(Value {
+                e: E::Float(f64::INFINITY),
+                start,
+                end,
+            })
+        } else if s == "-inf" {
+            Ok(Value {
+                e: E::Float(f64::NEG_INFINITY),
+                start,
+                end,
+            })
+        } else if s == "nan" {
+            Ok(Value {
+                e: E::Float(f64::NAN),
+                start,
+                end,
+            })
+        } else if s == "-nan" {
+            Ok(Value {
+                e: E::Float(-f64::NAN),
+                start,
+                end,
+            })
+        } else {
+            self.integer(s, 10).map(to_integer)
+        }
+    }
+
+    fn number_leading_plus(&mut self, Span { start, .. }: Span) -> Result<Value<'a>, Error> {
+        let start_token = self.tokens.current();
+        match self.next()? {
+            Some((Span { end, .. }, Token::Keylike(s))) => self.number(Span { start, end }, s),
+            _ => Err(self.error(start_token, ErrorKind::NumberInvalid)),
+        }
+    }
+
+    fn integer(&self, s: &'a str, radix: u32) -> Result<i64, Error> {
+        let allow_sign = radix == 10;
+        let allow_leading_zeros = radix != 10;
+        let (prefix, suffix) = self.parse_integer(s, allow_sign, allow_leading_zeros, radix)?;
+        let start = self.tokens.substr_offset(s);
+        if suffix != "" {
+            return Err(self.error(start, ErrorKind::NumberInvalid));
+        }
+        i64::from_str_radix(&prefix.replace("_", "").trim_start_matches('+'), radix)
+            .map_err(|_e| self.error(start, ErrorKind::NumberInvalid))
+    }
+
+    fn parse_integer(
+        &self,
+        s: &'a str,
+        allow_sign: bool,
+        allow_leading_zeros: bool,
+        radix: u32,
+    ) -> Result<(&'a str, &'a str), Error> {
+        let start = self.tokens.substr_offset(s);
+
+        let mut first = true;
+        let mut first_zero = false;
+        let mut underscore = false;
+        let mut end = s.len();
+        for (i, c) in s.char_indices() {
+            let at = i + start;
+            if i == 0 && (c == '+' || c == '-') && allow_sign {
+                continue;
+            }
+
+            if c == '0' && first {
+                first_zero = true;
+            } else if c.is_digit(radix) {
+                if !first && first_zero && !allow_leading_zeros {
+                    return Err(self.error(at, ErrorKind::NumberInvalid));
+                }
+                underscore = false;
+            } else if c == '_' && first {
+                return Err(self.error(at, ErrorKind::NumberInvalid));
+            } else if c == '_' && !underscore {
+                underscore = true;
+            } else {
+                end = i;
+                break;
+            }
+            first = false;
+        }
+        if first || underscore {
+            return Err(self.error(start, ErrorKind::NumberInvalid));
+        }
+        Ok((&s[..end], &s[end..]))
+    }
+
+    fn float(&mut self, s: &'a str, after_decimal: Option<&'a str>) -> Result<f64, Error> {
+        let (integral, mut suffix) = self.parse_integer(s, true, false, 10)?;
+        let start = self.tokens.substr_offset(integral);
+
+        let mut fraction = None;
+        if let Some(after) = after_decimal {
+            if suffix != "" {
+                return Err(self.error(start, ErrorKind::NumberInvalid));
+            }
+            let (a, b) = self.parse_integer(after, false, true, 10)?;
+            fraction = Some(a);
+            suffix = b;
+        }
+
+        let mut exponent = None;
+        if suffix.starts_with('e') || suffix.starts_with('E') {
+            let (a, b) = if suffix.len() == 1 {
+                self.eat(Token::Plus)?;
+                match self.next()? {
+                    Some((_, Token::Keylike(s))) => self.parse_integer(s, false, true, 10)?,
+                    _ => return Err(self.error(start, ErrorKind::NumberInvalid)),
+                }
+            } else {
+                self.parse_integer(&suffix[1..], true, true, 10)?
+            };
+            if b != "" {
+                return Err(self.error(start, ErrorKind::NumberInvalid));
+            }
+            exponent = Some(a);
+        } else if !suffix.is_empty() {
+            return Err(self.error(start, ErrorKind::NumberInvalid));
+        }
+
+        let mut number = integral
+            .trim_start_matches('+')
+            .chars()
+            .filter(|c| *c != '_')
+            .collect::<String>();
+        if let Some(fraction) = fraction {
+            number.push_str(".");
+            number.extend(fraction.chars().filter(|c| *c != '_'));
+        }
+        if let Some(exponent) = exponent {
+            number.push_str("E");
+            number.extend(exponent.chars().filter(|c| *c != '_'));
+        }
+        number
+            .parse()
+            .map_err(|_e| self.error(start, ErrorKind::NumberInvalid))
+            .and_then(|n: f64| {
+                if n.is_finite() {
+                    Ok(n)
+                } else {
+                    Err(self.error(start, ErrorKind::NumberInvalid))
+                }
+            })
+    }
+
+    fn datetime(
+        &mut self,
+        mut span: Span,
+        date: &'a str,
+        colon_eaten: bool,
+    ) -> Result<(Span, &'a str), Error> {
+        let start = self.tokens.substr_offset(date);
+
+        // Check for space separated date and time.
+        let mut lookahead = self.tokens.clone();
+        if let Ok(Some((_, Token::Whitespace(" ")))) = lookahead.next() {
+            // Check if hour follows.
+            if let Ok(Some((_, Token::Keylike(_)))) = lookahead.next() {
+                self.next()?; // skip space
+                self.next()?; // skip keylike hour
+            }
+        }
+
+        if colon_eaten || self.eat(Token::Colon)? {
+            // minutes
+            match self.next()? {
+                Some((_, Token::Keylike(_))) => {}
+                _ => return Err(self.error(start, ErrorKind::DateInvalid)),
+            }
+            // Seconds
+            self.expect(Token::Colon)?;
+            match self.next()? {
+                Some((Span { end, .. }, Token::Keylike(_))) => {
+                    span.end = end;
+                }
+                _ => return Err(self.error(start, ErrorKind::DateInvalid)),
+            }
+            // Fractional seconds
+            if self.eat(Token::Period)? {
+                match self.next()? {
+                    Some((Span { end, .. }, Token::Keylike(_))) => {
+                        span.end = end;
+                    }
+                    _ => return Err(self.error(start, ErrorKind::DateInvalid)),
+                }
+            }
+
+            // offset
+            if self.eat(Token::Plus)? {
+                match self.next()? {
+                    Some((Span { end, .. }, Token::Keylike(_))) => {
+                        span.end = end;
+                    }
+                    _ => return Err(self.error(start, ErrorKind::DateInvalid)),
+                }
+            }
+            if self.eat(Token::Colon)? {
+                match self.next()? {
+                    Some((Span { end, .. }, Token::Keylike(_))) => {
+                        span.end = end;
+                    }
+                    _ => return Err(self.error(start, ErrorKind::DateInvalid)),
+                }
+            }
+        }
+
+        let end = self.tokens.current();
+        Ok((span, &self.tokens.input()[start..end]))
+    }
+
+    // TODO(#140): shouldn't buffer up this entire table in memory, it'd be
+    // great to defer parsing everything until later.
+    fn inline_table(&mut self) -> Result<(Span, Vec<TablePair<'a>>), Error> {
+        let mut ret = Vec::new();
+        self.eat_whitespace()?;
+        if let Some(span) = self.eat_spanned(Token::RightBrace)? {
+            return Ok((span, ret));
+        }
+        loop {
+            let key = self.dotted_key()?;
+            self.eat_whitespace()?;
+            self.expect(Token::Equals)?;
+            self.eat_whitespace()?;
+            let value = self.value()?;
+            self.add_dotted_key(key, value, &mut ret)?;
+
+            self.eat_whitespace()?;
+            if let Some(span) = self.eat_spanned(Token::RightBrace)? {
+                return Ok((span, ret));
+            }
+            self.expect(Token::Comma)?;
+            self.eat_whitespace()?;
+        }
+    }
+
+    // TODO(#140): shouldn't buffer up this entire array in memory, it'd be
+    // great to defer parsing everything until later.
+    fn array(&mut self) -> Result<(Span, Vec<Value<'a>>), Error> {
+        let mut ret = Vec::new();
+
+        let intermediate = |me: &mut Deserializer<'_>| {
+            loop {
+                me.eat_whitespace()?;
+                if !me.eat(Token::Newline)? && !me.eat_comment()? {
+                    break;
+                }
+            }
+            Ok(())
+        };
+
+        loop {
+            intermediate(self)?;
+            if let Some(span) = self.eat_spanned(Token::RightBracket)? {
+                return Ok((span, ret));
+            }
+            let value = self.value()?;
+            ret.push(value);
+            intermediate(self)?;
+            if !self.eat(Token::Comma)? {
+                break;
+            }
+        }
+        intermediate(self)?;
+        let span = self.expect_spanned(Token::RightBracket)?;
+        Ok((span, ret))
+    }
+
+    fn table_key(&mut self) -> Result<(Span, Cow<'a, str>), Error> {
+        self.tokens.table_key().map_err(|e| self.token_error(e))
+    }
+
+    fn dotted_key(&mut self) -> Result<Vec<(Span, Cow<'a, str>)>, Error> {
+        let mut result = Vec::new();
+        result.push(self.table_key()?);
+        self.eat_whitespace()?;
+        while self.eat(Token::Period)? {
+            self.eat_whitespace()?;
+            result.push(self.table_key()?);
+            self.eat_whitespace()?;
+        }
+        Ok(result)
+    }
+
+    /// Stores a value in the appropriate hierarchical structure positioned based on the dotted key.
+    ///
+    /// Given the following definition: `multi.part.key = "value"`, `multi` and `part` are
+    /// intermediate parts which are mapped to the relevant fields in the deserialized type's data
+    /// hierarchy.
+    ///
+    /// # Parameters
+    ///
+    /// * `key_parts`: Each segment of the dotted key, e.g. `part.one` maps to
+    ///                `vec![Cow::Borrowed("part"), Cow::Borrowed("one")].`
+    /// * `value`: The parsed value.
+    /// * `values`: The `Vec` to store the value in.
+    fn add_dotted_key(
+        &self,
+        mut key_parts: Vec<(Span, Cow<'a, str>)>,
+        value: Value<'a>,
+        values: &mut Vec<TablePair<'a>>,
+    ) -> Result<(), Error> {
+        let key = key_parts.remove(0);
+        if key_parts.is_empty() {
+            values.push((key, value));
+            return Ok(());
+        }
+        match values.iter_mut().find(|&&mut (ref k, _)| *k.1 == key.1) {
+            Some(&mut (
+                _,
+                Value {
+                    e: E::DottedTable(ref mut v),
+                    ..
+                },
+            )) => {
+                return self.add_dotted_key(key_parts, value, v);
+            }
+            Some(&mut (_, Value { start, .. })) => {
+                return Err(self.error(start, ErrorKind::DottedKeyInvalidType));
+            }
+            None => {}
+        }
+        // The start/end value is somewhat misleading here.
+        let table_values = Value {
+            e: E::DottedTable(Vec::new()),
+            start: value.start,
+            end: value.end,
+        };
+        values.push((key, table_values));
+        let last_i = values.len() - 1;
+        if let (
+            _,
+            Value {
+                e: E::DottedTable(ref mut v),
+                ..
+            },
+        ) = values[last_i]
+        {
+            self.add_dotted_key(key_parts, value, v)?;
+        }
+        Ok(())
+    }
+
+    fn eat_whitespace(&mut self) -> Result<(), Error> {
+        self.tokens
+            .eat_whitespace()
+            .map_err(|e| self.token_error(e))
+    }
+
+    fn eat_comment(&mut self) -> Result<bool, Error> {
+        self.tokens.eat_comment().map_err(|e| self.token_error(e))
+    }
+
+    fn eat_newline_or_eof(&mut self) -> Result<(), Error> {
+        self.tokens
+            .eat_newline_or_eof()
+            .map_err(|e| self.token_error(e))
+    }
+
+    fn eat(&mut self, expected: Token<'a>) -> Result<bool, Error> {
+        self.tokens.eat(expected).map_err(|e| self.token_error(e))
+    }
+
+    fn eat_spanned(&mut self, expected: Token<'a>) -> Result<Option<Span>, Error> {
+        self.tokens
+            .eat_spanned(expected)
+            .map_err(|e| self.token_error(e))
+    }
+
+    fn expect(&mut self, expected: Token<'a>) -> Result<(), Error> {
+        self.tokens
+            .expect(expected)
+            .map_err(|e| self.token_error(e))
+    }
+
+    fn expect_spanned(&mut self, expected: Token<'a>) -> Result<Span, Error> {
+        self.tokens
+            .expect_spanned(expected)
+            .map_err(|e| self.token_error(e))
+    }
+
+    fn next(&mut self) -> Result<Option<(Span, Token<'a>)>, Error> {
+        self.tokens.next().map_err(|e| self.token_error(e))
+    }
+
+    fn peek(&mut self) -> Result<Option<(Span, Token<'a>)>, Error> {
+        self.tokens.peek().map_err(|e| self.token_error(e))
+    }
+
+    fn eof(&self) -> Error {
+        self.error(self.input.len(), ErrorKind::UnexpectedEof)
+    }
+
+    fn token_error(&self, error: TokenError) -> Error {
+        match error {
+            TokenError::InvalidCharInString(at, ch) => {
+                self.error(at, ErrorKind::InvalidCharInString(ch))
+            }
+            TokenError::InvalidEscape(at, ch) => self.error(at, ErrorKind::InvalidEscape(ch)),
+            TokenError::InvalidEscapeValue(at, v) => {
+                self.error(at, ErrorKind::InvalidEscapeValue(v))
+            }
+            TokenError::InvalidHexEscape(at, ch) => self.error(at, ErrorKind::InvalidHexEscape(ch)),
+            TokenError::NewlineInString(at) => self.error(at, ErrorKind::NewlineInString),
+            TokenError::Unexpected(at, ch) => self.error(at, ErrorKind::Unexpected(ch)),
+            TokenError::UnterminatedString(at) => self.error(at, ErrorKind::UnterminatedString),
+            TokenError::NewlineInTableKey(at) => self.error(at, ErrorKind::NewlineInTableKey),
+            TokenError::Wanted {
+                at,
+                expected,
+                found,
+            } => self.error(at, ErrorKind::Wanted { expected, found }),
+            TokenError::MultilineStringKey(at) => self.error(at, ErrorKind::MultilineStringKey),
+        }
+    }
+
+    fn error(&self, at: usize, kind: ErrorKind) -> Error {
+        let mut err = Error::from_kind(Some(at), kind);
+        err.fix_linecol(|at| self.to_linecol(at));
+        err
+    }
+
+    /// Converts a byte offset from an error message to a (line, column) pair
+    ///
+    /// All indexes are 0-based.
+    fn to_linecol(&self, offset: usize) -> (usize, usize) {
+        let mut cur = 0;
+        // Use split_terminator instead of lines so that if there is a `\r`,
+        // it is included in the offset calculation. The `+1` values below
+        // account for the `\n`.
+        for (i, line) in self.input.split_terminator('\n').enumerate() {
+            if cur + line.len() + 1 > offset {
+                return (i, offset - cur);
+            }
+            cur += line.len() + 1;
+        }
+        (self.input.lines().count(), 0)
+    }
+}
+
+impl Error {
+    /// Produces a (line, column) pair of the position of the error if available
+    ///
+    /// All indexes are 0-based.
+    pub fn line_col(&self) -> Option<(usize, usize)> {
+        self.inner.line.map(|line| (line, self.inner.col))
+    }
+
+    fn from_kind(at: Option<usize>, kind: ErrorKind) -> Error {
+        Error {
+            inner: Box::new(ErrorInner {
+                kind,
+                line: None,
+                col: 0,
+                at,
+                message: String::new(),
+                key: Vec::new(),
+            }),
+        }
+    }
+
+    fn custom(at: Option<usize>, s: String) -> Error {
+        Error {
+            inner: Box::new(ErrorInner {
+                kind: ErrorKind::Custom,
+                line: None,
+                col: 0,
+                at,
+                message: s,
+                key: Vec::new(),
+            }),
+        }
+    }
+
+    pub(crate) fn add_key_context(&mut self, key: &str) {
+        self.inner.key.insert(0, key.to_string());
+    }
+
+    fn fix_offset<F>(&mut self, f: F)
+    where
+        F: FnOnce() -> Option<usize>,
+    {
+        // An existing offset is always better positioned than anything we
+        // might want to add later.
+        if self.inner.at.is_none() {
+            self.inner.at = f();
+        }
+    }
+
+    fn fix_linecol<F>(&mut self, f: F)
+    where
+        F: FnOnce(usize) -> (usize, usize),
+    {
+        if let Some(at) = self.inner.at {
+            let (line, col) = f(at);
+            self.inner.line = Some(line);
+            self.inner.col = col;
+        }
+    }
+}
+
+impl std::convert::From<Error> for std::io::Error {
+    fn from(e: Error) -> Self {
+        std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match &self.inner.kind {
+            ErrorKind::UnexpectedEof => "unexpected eof encountered".fmt(f)?,
+            ErrorKind::InvalidCharInString(c) => write!(
+                f,
+                "invalid character in string: `{}`",
+                c.escape_default().collect::<String>()
+            )?,
+            ErrorKind::InvalidEscape(c) => write!(
+                f,
+                "invalid escape character in string: `{}`",
+                c.escape_default().collect::<String>()
+            )?,
+            ErrorKind::InvalidHexEscape(c) => write!(
+                f,
+                "invalid hex escape character in string: `{}`",
+                c.escape_default().collect::<String>()
+            )?,
+            ErrorKind::InvalidEscapeValue(c) => write!(f, "invalid escape value: `{}`", c)?,
+            ErrorKind::NewlineInString => "newline in string found".fmt(f)?,
+            ErrorKind::Unexpected(ch) => write!(
+                f,
+                "unexpected character found: `{}`",
+                ch.escape_default().collect::<String>()
+            )?,
+            ErrorKind::UnterminatedString => "unterminated string".fmt(f)?,
+            ErrorKind::NewlineInTableKey => "found newline in table key".fmt(f)?,
+            ErrorKind::Wanted { expected, found } => {
+                write!(f, "expected {}, found {}", expected, found)?
+            }
+            ErrorKind::NumberInvalid => "invalid number".fmt(f)?,
+            ErrorKind::DateInvalid => "invalid date".fmt(f)?,
+            ErrorKind::DuplicateTable(ref s) => {
+                write!(f, "redefinition of table `{}`", s)?;
+            }
+            ErrorKind::RedefineAsArray => "table redefined as array".fmt(f)?,
+            ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?,
+            ErrorKind::MultilineStringKey => "multiline strings are not allowed for key".fmt(f)?,
+            ErrorKind::Custom => self.inner.message.fmt(f)?,
+            ErrorKind::ExpectedTuple(l) => write!(f, "expected table with length {}", l)?,
+            ErrorKind::ExpectedTupleIndex {
+                expected,
+                ref found,
+            } => write!(f, "expected table key `{}`, but was `{}`", expected, found)?,
+            ErrorKind::ExpectedEmptyTable => "expected empty table".fmt(f)?,
+            ErrorKind::DottedKeyInvalidType => {
+                "dotted key attempted to extend non-table type".fmt(f)?
+            }
+            ErrorKind::UnexpectedKeys {
+                ref keys,
+                available,
+            } => write!(
+                f,
+                "unexpected keys in table: `{:?}`, available keys: `{:?}`",
+                keys, available
+            )?,
+            ErrorKind::UnquotedString => write!(
+                f,
+                "invalid TOML value, did you mean to use a quoted string?"
+            )?,
+            ErrorKind::__Nonexhaustive => panic!(),
+        }
+
+        if !self.inner.key.is_empty() {
+            write!(f, " for key `")?;
+            for (i, k) in self.inner.key.iter().enumerate() {
+                if i > 0 {
+                    write!(f, ".")?;
+                }
+                write!(f, "{}", k)?;
+            }
+            write!(f, "`")?;
+        }
+
+        if let Some(line) = self.inner.line {
+            write!(f, " at line {} column {}", line + 1, self.inner.col + 1)?;
+        }
+
+        Ok(())
+    }
+}
+
+impl error::Error for Error {}
+
+impl de::Error for Error {
+    fn custom<T: fmt::Display>(msg: T) -> Error {
+        Error::custom(None, msg.to_string())
+    }
+}
+
+enum Line<'a> {
+    Table {
+        at: usize,
+        header: Header<'a>,
+        array: bool,
+    },
+    KeyValue(Vec<(Span, Cow<'a, str>)>, Value<'a>),
+}
+
+struct Header<'a> {
+    first: bool,
+    array: bool,
+    require_newline_after_table: bool,
+    tokens: Tokenizer<'a>,
+}
+
+impl<'a> Header<'a> {
+    fn new(tokens: Tokenizer<'a>, array: bool, require_newline_after_table: bool) -> Header<'a> {
+        Header {
+            first: true,
+            array,
+            tokens,
+            require_newline_after_table,
+        }
+    }
+
+    fn next(&mut self) -> Result<Option<(Span, Cow<'a, str>)>, TokenError> {
+        self.tokens.eat_whitespace()?;
+
+        if self.first || self.tokens.eat(Token::Period)? {
+            self.first = false;
+            self.tokens.eat_whitespace()?;
+            self.tokens.table_key().map(|t| t).map(Some)
+        } else {
+            self.tokens.expect(Token::RightBracket)?;
+            if self.array {
+                self.tokens.expect(Token::RightBracket)?;
+            }
+
+            self.tokens.eat_whitespace()?;
+            if self.require_newline_after_table && !self.tokens.eat_comment()? {
+                self.tokens.eat_newline_or_eof()?;
+            }
+            Ok(None)
+        }
+    }
+}
+
+#[derive(Debug)]
+struct Value<'a> {
+    e: E<'a>,
+    start: usize,
+    end: usize,
+}
+
+#[derive(Debug)]
+enum E<'a> {
+    Integer(i64),
+    Float(f64),
+    Boolean(bool),
+    String(Cow<'a, str>),
+    Datetime(&'a str),
+    Array(Vec<Value<'a>>),
+    InlineTable(Vec<TablePair<'a>>),
+    DottedTable(Vec<TablePair<'a>>),
+}
+
+impl<'a> E<'a> {
+    fn type_name(&self) -> &'static str {
+        match *self {
+            E::String(..) => "string",
+            E::Integer(..) => "integer",
+            E::Float(..) => "float",
+            E::Boolean(..) => "boolean",
+            E::Datetime(..) => "datetime",
+            E::Array(..) => "array",
+            E::InlineTable(..) => "inline table",
+            E::DottedTable(..) => "dotted table",
+        }
+    }
+}
diff --git a/crates/toml/src/lib.rs b/crates/toml/src/lib.rs
new file mode 100644
index 00000000..00421b11
--- /dev/null
+++ b/crates/toml/src/lib.rs
@@ -0,0 +1,180 @@
+//! A [TOML]-parsing library
+//!
+//! This library implements a [TOML] v0.5.0 compatible parser,
+//! primarily supporting the [`serde`] library for encoding/decoding
+//! various types in Rust.
+//!
+//! TOML itself is a simple, ergonomic, and readable configuration format:
+//!
+//! ```toml
+//! [package]
+//! name = "toml"
+//! version = "0.4.2"
+//! authors = ["Alex Crichton <alex@alexcrichton.com>"]
+//!
+//! [dependencies]
+//! serde = "1.0"
+//! ```
+//!
+//! The TOML format tends to be relatively common throughout the Rust community
+//! for configuration, notably being used by [Cargo], Rust's package manager.
+//!
+//! ## TOML values
+//!
+//! A value in TOML is represented with the [`Value`] enum in this crate:
+//!
+//! ```rust,ignore
+//! pub enum Value {
+//!     String(String),
+//!     Integer(i64),
+//!     Float(f64),
+//!     Boolean(bool),
+//!     Datetime(Datetime),
+//!     Array(Array),
+//!     Table(Table),
+//! }
+//! ```
+//!
+//! TOML is similar to JSON with the notable addition of a [`Datetime`]
+//! type. In general, TOML and JSON are interchangeable in terms of
+//! formats.
+//!
+//! ## Parsing TOML
+//!
+//! The easiest way to parse a TOML document is via the [`Value`] type:
+//!
+//! ```rust
+//! use toml::Value;
+//!
+//! let value = "foo = 'bar'".parse::<Value>().unwrap();
+//!
+//! assert_eq!(value["foo"].as_str(), Some("bar"));
+//! ```
+//!
+//! The [`Value`] type implements a number of convenience methods and
+//! traits; the example above uses [`FromStr`] to parse a [`str`] into a
+//! [`Value`].
+//!
+//! ## Deserialization and Serialization
+//!
+//! This crate supports [`serde`] 1.0 with a number of
+//! implementations of the `Deserialize`, `Serialize`, `Deserializer`, and
+//! `Serializer` traits. Namely, you'll find:
+//!
+//! * `Deserialize for Value`
+//! * `Serialize for Value`
+//! * `Deserialize for Datetime`
+//! * `Serialize for Datetime`
+//! * `Deserializer for de::Deserializer`
+//! * `Serializer for ser::Serializer`
+//! * `Deserializer for Value`
+//!
+//! This means that you can use Serde to deserialize/serialize the
+//! [`Value`] type as well as the [`Datetime`] type in this crate. You can also
+//! use the [`Deserializer`], [`Serializer`], or [`Value`] type itself to act as
+//! a deserializer/serializer for arbitrary types.
+//!
+//! An example of deserializing with TOML is:
+//!
+//! ```rust
+//! use serde_derive::Deserialize;
+//!
+//! #[derive(Deserialize)]
+//! struct Config {
+//!     ip: String,
+//!     port: Option<u16>,
+//!     keys: Keys,
+//! }
+//!
+//! #[derive(Deserialize)]
+//! struct Keys {
+//!     github: String,
+//!     travis: Option<String>,
+//! }
+//!
+//! fn main() {
+//!     let config: Config = toml::from_str(r#"
+//!         ip = '127.0.0.1'
+//!
+//!         [keys]
+//!         github = 'xxxxxxxxxxxxxxxxx'
+//!         travis = 'yyyyyyyyyyyyyyyyy'
+//!     "#).unwrap();
+//!
+//!     assert_eq!(config.ip, "127.0.0.1");
+//!     assert_eq!(config.port, None);
+//!     assert_eq!(config.keys.github, "xxxxxxxxxxxxxxxxx");
+//!     assert_eq!(config.keys.travis.as_ref().unwrap(), "yyyyyyyyyyyyyyyyy");
+//! }
+//! ```
+//!
+//! You can serialize types in a similar fashion:
+//!
+//! ```rust
+//! use serde_derive::Serialize;
+//!
+//! #[derive(Serialize)]
+//! struct Config {
+//!     ip: String,
+//!     port: Option<u16>,
+//!     keys: Keys,
+//! }
+//!
+//! #[derive(Serialize)]
+//! struct Keys {
+//!     github: String,
+//!     travis: Option<String>,
+//! }
+//!
+//! fn main() {
+//!     let config = Config {
+//!         ip: "127.0.0.1".to_string(),
+//!         port: None,
+//!         keys: Keys {
+//!             github: "xxxxxxxxxxxxxxxxx".to_string(),
+//!             travis: Some("yyyyyyyyyyyyyyyyy".to_string()),
+//!         },
+//!     };
+//!
+//!     let toml = toml::to_string(&config).unwrap();
+//! }
+//! ```
+//!
+//! [TOML]: https://github.com/toml-lang/toml
+//! [Cargo]: https://crates.io/
+//! [`serde`]: https://serde.rs/
+
+#![doc(html_root_url = "https://docs.rs/toml/0.5")]
+#![deny(missing_docs)]
+#![warn(rust_2018_idioms)]
+// Makes rustc abort compilation if there are any unsafe blocks in the crate.
+// Presence of this annotation is picked up by tools such as cargo-geiger
+// and lets them ensure that there is indeed no unsafe code as opposed to
+// something they couldn't detect (e.g. unsafe added via macro expansion, etc).
+#![forbid(unsafe_code)]
+
+pub mod map;
+pub mod value;
+#[doc(no_inline)]
+pub use crate::value::Value;
+mod datetime;
+
+pub mod ser;
+#[doc(no_inline)]
+pub use crate::ser::{to_string, to_string_pretty, to_vec, Serializer};
+pub mod de;
+#[doc(no_inline)]
+pub use crate::de::{from_slice, from_str, Deserializer};
+mod tokens;
+
+#[doc(hidden)]
+pub mod macros;
+
+mod spanned;
+pub use crate::spanned::Spanned;
+
+// Just for rustdoc
+#[allow(unused_imports)]
+use crate::datetime::Datetime;
+#[allow(unused_imports)]
+use core::str::FromStr;
diff --git a/crates/toml/src/macros.rs b/crates/toml/src/macros.rs
new file mode 100644
index 00000000..0731afef
--- /dev/null
+++ b/crates/toml/src/macros.rs
@@ -0,0 +1,462 @@
+pub use serde::de::{Deserialize, IntoDeserializer};
+
+use crate::value::{Array, Table, Value};
+
+/// Construct a [`toml::Value`] from TOML syntax.
+///
+/// [`toml::Value`]: value/enum.Value.html
+///
+/// ```rust
+/// fn main() {
+///     let cargo_toml = toml::toml! {
+///         [package]
+///         name = "toml"
+///         version = "0.4.5"
+///         authors = ["Alex Crichton <alex@alexcrichton.com>"]
+///
+///         [badges]
+///         travis-ci = { repository = "alexcrichton/toml-rs" }
+///
+///         [dependencies]
+///         serde = "1.0"
+///
+///         [dev-dependencies]
+///         serde_derive = "1.0"
+///         serde_json = "1.0"
+///     };
+///
+///     println!("{:#?}", cargo_toml);
+/// }
+/// ```
+#[macro_export]
+macro_rules! toml {
+    ($($toml:tt)+) => {{
+        let table = $crate::value::Table::new();
+        let mut root = $crate::Value::Table(table);
+        $crate::toml_internal!(@toplevel root [] $($toml)+);
+        root
+    }};
+}
+
+// TT-muncher to parse TOML syntax into a toml::Value.
+//
+//    @toplevel -- Parse tokens outside of an inline table or inline array. In
+//                 this state, `[table headers]` and `[[array headers]]` are
+//                 allowed and `key = value` pairs are not separated by commas.
+//
+//    @topleveldatetime -- Helper to parse a Datetime from string and insert it
+//                 into a table, continuing in the @toplevel state.
+//
+//    @path -- Turn a path segment into a string. Segments that look like idents
+//                 are stringified, while quoted segments like `"cfg(windows)"`
+//                 are not.
+//
+//    @value -- Parse the value part of a `key = value` pair, which may be a
+//                 primitive or inline table or inline array.
+//
+//    @table -- Parse the contents of an inline table, returning them as a
+//                 toml::Value::Table.
+//
+//    @tabledatetime -- Helper to parse a Datetime from string and insert it
+//                 into a table, continuing in the @table state.
+//
+//    @array -- Parse the contents of an inline array, returning them as a
+//                 toml::Value::Array.
+//
+//    @arraydatetime -- Helper to parse a Datetime from string and push it into
+//                 an array, continuing in the @array state.
+//
+//    @trailingcomma -- Helper to append a comma to a sequence of tokens if the
+//                 sequence is non-empty and does not already end in a trailing
+//                 comma.
+//
+#[macro_export]
+#[doc(hidden)]
+macro_rules! toml_internal {
+    // Base case, no elements remaining.
+    (@toplevel $root:ident [$($path:tt)*]) => {};
+
+    // Parse negative number `key = -value`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = - $v:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@toplevel $root [$($path)*] $($($k)-+).+ = (-$v) $($rest)*);
+    };
+
+    // Parse positive number `key = +value`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = + $v:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@toplevel $root [$($path)*] $($($k)-+).+ = ($v) $($rest)*);
+    };
+
+    // Parse offset datetime `key = 1979-05-27T00:32:00.999999-07:00`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*);
+    };
+    // Space instead of T.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec . $frac - $tzh : $tzm) $($rest)*);
+    };
+
+    // Parse offset datetime `key = 1979-05-27T00:32:00-07:00`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*);
+    };
+    // Space instead of T.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec - $tzh : $tzm) $($rest)*);
+    };
+
+    // Parse local datetime `key = 1979-05-27T00:32:00.999999`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*);
+    };
+    // Space instead of T.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec . $frac) $($rest)*);
+    };
+
+    // Parse offset datetime `key = 1979-05-27T07:32:00Z` and local datetime `key = 1979-05-27T07:32:00`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec) $($rest)*);
+    };
+    // Space instead of T.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec) $($rest)*);
+    };
+
+    // Parse local date `key = 1979-05-27`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($yr - $mo - $day) $($rest)*);
+    };
+
+    // Parse local time `key = 00:32:00.999999`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $hr:tt : $min:tt : $sec:tt . $frac:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($hr : $min : $sec . $frac) $($rest)*);
+    };
+
+    // Parse local time `key = 07:32:00`.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $hr:tt : $min:tt : $sec:tt $($rest:tt)*) => {
+        $crate::toml_internal!(@topleveldatetime $root [$($path)*] $($($k)-+).+ = ($hr : $min : $sec) $($rest)*);
+    };
+
+    // Parse any other `key = value` including string, inline array, inline
+    // table, number, and boolean.
+    (@toplevel $root:ident [$($path:tt)*] $($($k:tt)-+).+ = $v:tt $($rest:tt)*) => {{
+        $crate::macros::insert_toml(
+            &mut $root,
+            &[$($path)* $(&concat!($("-", $crate::toml_internal!(@path $k),)+)[1..], )+],
+            $crate::toml_internal!(@value $v));
+        $crate::toml_internal!(@toplevel $root [$($path)*] $($rest)*);
+    }};
+
+    // Parse array header `[[bin]]`.
+    (@toplevel $root:ident $oldpath:tt [[$($($path:tt)-+).+]] $($rest:tt)*) => {
+        $crate::macros::push_toml(
+            &mut $root,
+            &[$(&concat!($("-", $crate::toml_internal!(@path $path),)+)[1..],)+]);
+        $crate::toml_internal!(@toplevel $root [$(&concat!($("-", $crate::toml_internal!(@path $path),)+)[1..],)+] $($rest)*);
+    };
+
+    // Parse table header `[patch.crates-io]`.
+    (@toplevel $root:ident $oldpath:tt [$($($path:tt)-+).+] $($rest:tt)*) => {
+        $crate::macros::insert_toml(
+            &mut $root,
+            &[$(&concat!($("-", $crate::toml_internal!(@path $path),)+)[1..],)+],
+            $crate::Value::Table($crate::value::Table::new()));
+        $crate::toml_internal!(@toplevel $root [$(&concat!($("-", $crate::toml_internal!(@path $path),)+)[1..],)+] $($rest)*);
+    };
+
+    // Parse datetime from string and insert into table.
+    (@topleveldatetime $root:ident [$($path:tt)*] $($($k:tt)-+).+ = ($($datetime:tt)+) $($rest:tt)*) => {
+        $crate::macros::insert_toml(
+            &mut $root,
+            &[$($path)* $(&concat!($("-", $crate::toml_internal!(@path $k),)+)[1..], )+],
+            $crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap()));
+        $crate::toml_internal!(@toplevel $root [$($path)*] $($rest)*);
+    };
+
+    // Turn a path segment into a string.
+    (@path $ident:ident) => {
+        stringify!($ident)
+    };
+
+    // For a path segment that is not an ident, expect that it is already a
+    // quoted string, like in `[target."cfg(windows)".dependencies]`.
+    (@path $quoted:tt) => {
+        $quoted
+    };
+
+    // Construct a Value from an inline table.
+    (@value { $($inline:tt)* }) => {{
+        let mut table = $crate::Value::Table($crate::value::Table::new());
+        $crate::toml_internal!(@trailingcomma (@table table) $($inline)*);
+        table
+    }};
+
+    // Construct a Value from an inline array.
+    (@value [ $($inline:tt)* ]) => {{
+        let mut array = $crate::value::Array::new();
+        $crate::toml_internal!(@trailingcomma (@array array) $($inline)*);
+        $crate::Value::Array(array)
+    }};
+
+    (@value (-nan)) => {
+        $crate::Value::Float(-::std::f64::NAN)
+    };
+
+    (@value (nan)) => {
+        $crate::Value::Float(::std::f64::NAN)
+    };
+
+    (@value nan) => {
+        $crate::Value::Float(::std::f64::NAN)
+    };
+
+    (@value (-inf)) => {
+        $crate::Value::Float(::std::f64::NEG_INFINITY)
+    };
+
+    (@value (inf)) => {
+        $crate::Value::Float(::std::f64::INFINITY)
+    };
+
+    (@value inf) => {
+        $crate::Value::Float(::std::f64::INFINITY)
+    };
+
+    // Construct a Value from any other type, probably string or boolean or number.
+    (@value $v:tt) => {{
+        // TODO: Implement this with something like serde_json::to_value instead.
+        let de = $crate::macros::IntoDeserializer::<$crate::de::Error>::into_deserializer($v);
+        <$crate::Value as $crate::macros::Deserialize>::deserialize(de).unwrap()
+    }};
+
+    // Base case of inline table.
+    (@table $root:ident) => {};
+
+    // Parse negative number `key = -value`.
+    (@table $root:ident $($($k:tt)-+).+ = - $v:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@table $root $($($k)-+).+ = (-$v) , $($rest)*);
+    };
+
+    // Parse positive number `key = +value`.
+    (@table $root:ident $($($k:tt)-+).+ = + $v:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@table $root $($($k)-+).+ = ($v) , $($rest)*);
+    };
+
+    // Parse offset datetime `key = 1979-05-27T00:32:00.999999-07:00`.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*);
+    };
+    // Space instead of T.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec . $frac - $tzh : $tzm) $($rest)*);
+    };
+
+    // Parse offset datetime `key = 1979-05-27T00:32:00-07:00`.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*);
+    };
+    // Space instead of T.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec - $tzh : $tzm) $($rest)*);
+    };
+
+    // Parse local datetime `key = 1979-05-27T00:32:00.999999`.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*);
+    };
+    // Space instead of T.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec . $frac) $($rest)*);
+    };
+
+    // Parse offset datetime `key = 1979-05-27T07:32:00Z` and local datetime `key = 1979-05-27T07:32:00`.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $dhr : $min : $sec) $($rest)*);
+    };
+    // Space instead of T.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day T $hr : $min : $sec) $($rest)*);
+    };
+
+    // Parse local date `key = 1979-05-27`.
+    (@table $root:ident $($($k:tt)-+).+ = $yr:tt - $mo:tt - $day:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($yr - $mo - $day) $($rest)*);
+    };
+
+    // Parse local time `key = 00:32:00.999999`.
+    (@table $root:ident $($($k:tt)-+).+ = $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($hr : $min : $sec . $frac) $($rest)*);
+    };
+
+    // Parse local time `key = 07:32:00`.
+    (@table $root:ident $($($k:tt)-+).+ = $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@tabledatetime $root $($($k)-+).+ = ($hr : $min : $sec) $($rest)*);
+    };
+
+    // Parse any other type, probably string or boolean or number.
+    (@table $root:ident $($($k:tt)-+).+ = $v:tt , $($rest:tt)*) => {
+        $crate::macros::insert_toml(
+            &mut $root,
+            &[$(&concat!($("-", $crate::toml_internal!(@path $k),)+)[1..], )+],
+            $crate::toml_internal!(@value $v));
+        $crate::toml_internal!(@table $root $($rest)*);
+    };
+
+    // Parse a Datetime from string and continue in @table state.
+    (@tabledatetime $root:ident $($($k:tt)-+).+ = ($($datetime:tt)*) $($rest:tt)*) => {
+        $crate::macros::insert_toml(
+            &mut $root,
+            &[$(&concat!($("-", $crate::toml_internal!(@path $k),)+)[1..], )+],
+            $crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap()));
+        $crate::toml_internal!(@table $root $($rest)*);
+    };
+
+    // Base case of inline array.
+    (@array $root:ident) => {};
+
+    // Parse negative number `-value`.
+    (@array $root:ident - $v:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@array $root (-$v) , $($rest)*);
+    };
+
+    // Parse positive number `+value`.
+    (@array $root:ident + $v:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@array $root ($v) , $($rest)*);
+    };
+
+    // Parse offset datetime `1979-05-27T00:32:00.999999-07:00`.
+    (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec . $frac - $tzh : $tzm) $($rest)*);
+    };
+    // Space instead of T.
+    (@array $root:ident $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day T $hr : $min : $sec . $frac - $tzh : $tzm) $($rest)*);
+    };
+
+    // Parse offset datetime `1979-05-27T00:32:00-07:00`.
+    (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec - $tzh : $tzm) $($rest)*);
+    };
+    // Space instead of T.
+    (@array $root:ident $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt - $tzh:tt : $tzm:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day T $hr : $min : $sec - $tzh : $tzm) $($rest)*);
+    };
+
+    // Parse local datetime `1979-05-27T00:32:00.999999`.
+    (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec . $frac) $($rest)*);
+    };
+    // Space instead of T.
+    (@array $root:ident $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day T $hr : $min : $sec . $frac) $($rest)*);
+    };
+
+    // Parse offset datetime `1979-05-27T07:32:00Z` and local datetime `1979-05-27T07:32:00`.
+    (@array $root:ident $yr:tt - $mo:tt - $dhr:tt : $min:tt : $sec:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $dhr : $min : $sec) $($rest)*);
+    };
+    // Space instead of T.
+    (@array $root:ident $yr:tt - $mo:tt - $day:tt $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day T $hr : $min : $sec) $($rest)*);
+    };
+
+    // Parse local date `1979-05-27`.
+    (@array $root:ident $yr:tt - $mo:tt - $day:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($yr - $mo - $day) $($rest)*);
+    };
+
+    // Parse local time `00:32:00.999999`.
+    (@array $root:ident $hr:tt : $min:tt : $sec:tt . $frac:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($hr : $min : $sec . $frac) $($rest)*);
+    };
+
+    // Parse local time `07:32:00`.
+    (@array $root:ident $hr:tt : $min:tt : $sec:tt , $($rest:tt)*) => {
+        $crate::toml_internal!(@arraydatetime $root ($hr : $min : $sec) $($rest)*);
+    };
+
+    // Parse any other type, probably string or boolean or number.
+    (@array $root:ident $v:tt , $($rest:tt)*) => {
+        $root.push($crate::toml_internal!(@value $v));
+        $crate::toml_internal!(@array $root $($rest)*);
+    };
+
+    // Parse a Datetime from string and continue in @array state.
+    (@arraydatetime $root:ident ($($datetime:tt)*) $($rest:tt)*) => {
+        $root.push($crate::Value::Datetime(concat!($(stringify!($datetime)),+).parse().unwrap()));
+        $crate::toml_internal!(@array $root $($rest)*);
+    };
+
+    // No trailing comma required if the tokens are empty.
+    (@trailingcomma ($($args:tt)*)) => {
+        $crate::toml_internal!($($args)*);
+    };
+
+    // Tokens end with a trailing comma, do not append another one.
+    (@trailingcomma ($($args:tt)*) ,) => {
+        $crate::toml_internal!($($args)* ,);
+    };
+
+    // Tokens end with something other than comma, append a trailing comma.
+    (@trailingcomma ($($args:tt)*) $last:tt) => {
+        $crate::toml_internal!($($args)* $last ,);
+    };
+
+    // Not yet at the last token.
+    (@trailingcomma ($($args:tt)*) $first:tt $($rest:tt)+) => {
+        $crate::toml_internal!(@trailingcomma ($($args)* $first) $($rest)+);
+    };
+}
+
+// Called when parsing a `key = value` pair.
+// Inserts an entry into the table at the given path.
+pub fn insert_toml(root: &mut Value, path: &[&str], value: Value) {
+    *traverse(root, path) = value;
+}
+
+// Called when parsing an `[[array header]]`.
+// Pushes an empty table onto the array at the given path.
+pub fn push_toml(root: &mut Value, path: &[&str]) {
+    let target = traverse(root, path);
+    if !target.is_array() {
+        *target = Value::Array(Array::new());
+    }
+    target
+        .as_array_mut()
+        .unwrap()
+        .push(Value::Table(Table::new()));
+}
+
+fn traverse<'a>(root: &'a mut Value, path: &[&str]) -> &'a mut Value {
+    let mut cur = root;
+    for &key in path {
+        // Lexical lifetimes :D
+        let cur1 = cur;
+        let cur2;
+
+        // From the TOML spec:
+        //
+        // > Each double-bracketed sub-table will belong to the most recently
+        // > defined table element above it.
+        if cur1.is_array() {
+            cur2 = cur1.as_array_mut().unwrap().last_mut().unwrap();
+        } else {
+            cur2 = cur1;
+        };
+
+        // We are about to index into this value, so it better be a table.
+        if !cur2.is_table() {
+            *cur2 = Value::Table(Table::new());
+        }
+
+        if !cur2.as_table().unwrap().contains_key(key) {
+            // Insert an empty table for the next loop iteration to point to.
+            let empty = Value::Table(Table::new());
+            cur2.as_table_mut().unwrap().insert(key.to_owned(), empty);
+        }
+
+        // Step into the current table.
+        cur = cur2.as_table_mut().unwrap().get_mut(key).unwrap();
+    }
+    cur
+}
diff --git a/crates/toml/src/map.rs b/crates/toml/src/map.rs
new file mode 100644
index 00000000..d130a1d5
--- /dev/null
+++ b/crates/toml/src/map.rs
@@ -0,0 +1,595 @@
+// Copyright 2017 Serde Developers
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! A map of String to toml::Value.
+//!
+//! By default the map is backed by a [`BTreeMap`]. Enable the `preserve_order`
+//! feature of toml-rs to use [`LinkedHashMap`] instead.
+//!
+//! [`BTreeMap`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
+//! [`LinkedHashMap`]: https://docs.rs/linked-hash-map/*/linked_hash_map/struct.LinkedHashMap.html
+
+use crate::value::Value;
+use serde::{de, ser};
+use std::borrow::Borrow;
+use std::fmt::{self, Debug};
+use std::hash::Hash;
+use std::iter::FromIterator;
+use std::ops;
+
+#[cfg(not(feature = "preserve_order"))]
+use std::collections::{btree_map, BTreeMap};
+
+#[cfg(feature = "preserve_order")]
+use indexmap::{self, IndexMap};
+
+/// Represents a TOML key/value type.
+pub struct Map<K, V> {
+    map: MapImpl<K, V>,
+}
+
+#[cfg(not(feature = "preserve_order"))]
+type MapImpl<K, V> = BTreeMap<K, V>;
+#[cfg(feature = "preserve_order")]
+type MapImpl<K, V> = IndexMap<K, V>;
+
+impl Map<String, Value> {
+    /// Makes a new empty Map.
+    #[inline]
+    pub fn new() -> Self {
+        Map {
+            map: MapImpl::new(),
+        }
+    }
+
+    #[cfg(not(feature = "preserve_order"))]
+    /// Makes a new empty Map with the given initial capacity.
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Self {
+        // does not support with_capacity
+        let _ = capacity;
+        Map {
+            map: BTreeMap::new(),
+        }
+    }
+
+    #[cfg(feature = "preserve_order")]
+    /// Makes a new empty Map with the given initial capacity.
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Self {
+        Map {
+            map: IndexMap::with_capacity(capacity),
+        }
+    }
+
+    /// Clears the map, removing all values.
+    #[inline]
+    pub fn clear(&mut self) {
+        self.map.clear()
+    }
+
+    /// Returns a reference to the value corresponding to the key.
+    ///
+    /// The key may be any borrowed form of the map's key type, but the ordering
+    /// on the borrowed form *must* match the ordering on the key type.
+    #[inline]
+    pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&Value>
+    where
+        String: Borrow<Q>,
+        Q: Ord + Eq + Hash,
+    {
+        self.map.get(key)
+    }
+
+    /// Returns true if the map contains a value for the specified key.
+    ///
+    /// The key may be any borrowed form of the map's key type, but the ordering
+    /// on the borrowed form *must* match the ordering on the key type.
+    #[inline]
+    pub fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool
+    where
+        String: Borrow<Q>,
+        Q: Ord + Eq + Hash,
+    {
+        self.map.contains_key(key)
+    }
+
+    /// Returns a mutable reference to the value corresponding to the key.
+    ///
+    /// The key may be any borrowed form of the map's key type, but the ordering
+    /// on the borrowed form *must* match the ordering on the key type.
+    #[inline]
+    pub fn get_mut<Q: ?Sized>(&mut self, key: &Q) -> Option<&mut Value>
+    where
+        String: Borrow<Q>,
+        Q: Ord + Eq + Hash,
+    {
+        self.map.get_mut(key)
+    }
+
+    /// Inserts a key-value pair into the map.
+    ///
+    /// If the map did not have this key present, `None` is returned.
+    ///
+    /// If the map did have this key present, the value is updated, and the old
+    /// value is returned. The key is not updated, though; this matters for
+    /// types that can be `==` without being identical.
+    #[inline]
+    pub fn insert(&mut self, k: String, v: Value) -> Option<Value> {
+        self.map.insert(k, v)
+    }
+
+    /// Removes a key from the map, returning the value at the key if the key
+    /// was previously in the map.
+    ///
+    /// The key may be any borrowed form of the map's key type, but the ordering
+    /// on the borrowed form *must* match the ordering on the key type.
+    #[inline]
+    pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<Value>
+    where
+        String: Borrow<Q>,
+        Q: Ord + Eq + Hash,
+    {
+        self.map.remove(key)
+    }
+
+    /// Gets the given key's corresponding entry in the map for in-place
+    /// manipulation.
+    pub fn entry<S>(&mut self, key: S) -> Entry<'_>
+    where
+        S: Into<String>,
+    {
+        #[cfg(feature = "preserve_order")]
+        use indexmap::map::Entry as EntryImpl;
+        #[cfg(not(feature = "preserve_order"))]
+        use std::collections::btree_map::Entry as EntryImpl;
+
+        match self.map.entry(key.into()) {
+            EntryImpl::Vacant(vacant) => Entry::Vacant(VacantEntry { vacant }),
+            EntryImpl::Occupied(occupied) => Entry::Occupied(OccupiedEntry { occupied }),
+        }
+    }
+
+    /// Returns the number of elements in the map.
+    #[inline]
+    pub fn len(&self) -> usize {
+        self.map.len()
+    }
+
+    /// Returns true if the map contains no elements.
+    #[inline]
+    pub fn is_empty(&self) -> bool {
+        self.map.is_empty()
+    }
+
+    /// Gets an iterator over the entries of the map.
+    #[inline]
+    pub fn iter(&self) -> Iter<'_> {
+        Iter {
+            iter: self.map.iter(),
+        }
+    }
+
+    /// Gets a mutable iterator over the entries of the map.
+    #[inline]
+    pub fn iter_mut(&mut self) -> IterMut<'_> {
+        IterMut {
+            iter: self.map.iter_mut(),
+        }
+    }
+
+    /// Gets an iterator over the keys of the map.
+    #[inline]
+    pub fn keys(&self) -> Keys<'_> {
+        Keys {
+            iter: self.map.keys(),
+        }
+    }
+
+    /// Gets an iterator over the values of the map.
+    #[inline]
+    pub fn values(&self) -> Values<'_> {
+        Values {
+            iter: self.map.values(),
+        }
+    }
+}
+
+impl Default for Map<String, Value> {
+    #[inline]
+    fn default() -> Self {
+        Map {
+            map: MapImpl::new(),
+        }
+    }
+}
+
+impl Clone for Map<String, Value> {
+    #[inline]
+    fn clone(&self) -> Self {
+        Map {
+            map: self.map.clone(),
+        }
+    }
+}
+
+impl PartialEq for Map<String, Value> {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        self.map.eq(&other.map)
+    }
+}
+
+/// Access an element of this map. Panics if the given key is not present in the
+/// map.
+impl<'a, Q: ?Sized> ops::Index<&'a Q> for Map<String, Value>
+where
+    String: Borrow<Q>,
+    Q: Ord + Eq + Hash,
+{
+    type Output = Value;
+
+    fn index(&self, index: &Q) -> &Value {
+        self.map.index(index)
+    }
+}
+
+/// Mutably access an element of this map. Panics if the given key is not
+/// present in the map.
+impl<'a, Q: ?Sized> ops::IndexMut<&'a Q> for Map<String, Value>
+where
+    String: Borrow<Q>,
+    Q: Ord + Eq + Hash,
+{
+    fn index_mut(&mut self, index: &Q) -> &mut Value {
+        self.map.get_mut(index).expect("no entry found for key")
+    }
+}
+
+impl Debug for Map<String, Value> {
+    #[inline]
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        self.map.fmt(formatter)
+    }
+}
+
+impl ser::Serialize for Map<String, Value> {
+    #[inline]
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        use serde::ser::SerializeMap;
+        let mut map = serializer.serialize_map(Some(self.len()))?;
+        for (k, v) in self {
+            map.serialize_key(k)?;
+            map.serialize_value(v)?;
+        }
+        map.end()
+    }
+}
+
+impl<'de> de::Deserialize<'de> for Map<String, Value> {
+    #[inline]
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        struct Visitor;
+
+        impl<'de> de::Visitor<'de> for Visitor {
+            type Value = Map<String, Value>;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                formatter.write_str("a map")
+            }
+
+            #[inline]
+            fn visit_unit<E>(self) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(Map::new())
+            }
+
+            #[inline]
+            fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
+            where
+                V: de::MapAccess<'de>,
+            {
+                let mut values = Map::new();
+
+                while let Some((key, value)) = visitor.next_entry()? {
+                    values.insert(key, value);
+                }
+
+                Ok(values)
+            }
+        }
+
+        deserializer.deserialize_map(Visitor)
+    }
+}
+
+impl FromIterator<(String, Value)> for Map<String, Value> {
+    fn from_iter<T>(iter: T) -> Self
+    where
+        T: IntoIterator<Item = (String, Value)>,
+    {
+        Map {
+            map: FromIterator::from_iter(iter),
+        }
+    }
+}
+
+impl Extend<(String, Value)> for Map<String, Value> {
+    fn extend<T>(&mut self, iter: T)
+    where
+        T: IntoIterator<Item = (String, Value)>,
+    {
+        self.map.extend(iter);
+    }
+}
+
+macro_rules! delegate_iterator {
+    (($name:ident $($generics:tt)*) => $item:ty) => {
+        impl $($generics)* Iterator for $name $($generics)* {
+            type Item = $item;
+            #[inline]
+            fn next(&mut self) -> Option<Self::Item> {
+                self.iter.next()
+            }
+            #[inline]
+            fn size_hint(&self) -> (usize, Option<usize>) {
+                self.iter.size_hint()
+            }
+        }
+
+        impl $($generics)* DoubleEndedIterator for $name $($generics)* {
+            #[inline]
+            fn next_back(&mut self) -> Option<Self::Item> {
+                self.iter.next_back()
+            }
+        }
+
+        impl $($generics)* ExactSizeIterator for $name $($generics)* {
+            #[inline]
+            fn len(&self) -> usize {
+                self.iter.len()
+            }
+        }
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+/// A view into a single entry in a map, which may either be vacant or occupied.
+/// This enum is constructed from the [`entry`] method on [`Map`].
+///
+/// [`entry`]: struct.Map.html#method.entry
+/// [`Map`]: struct.Map.html
+pub enum Entry<'a> {
+    /// A vacant Entry.
+    Vacant(VacantEntry<'a>),
+    /// An occupied Entry.
+    Occupied(OccupiedEntry<'a>),
+}
+
+/// A vacant Entry. It is part of the [`Entry`] enum.
+///
+/// [`Entry`]: enum.Entry.html
+pub struct VacantEntry<'a> {
+    vacant: VacantEntryImpl<'a>,
+}
+
+/// An occupied Entry. It is part of the [`Entry`] enum.
+///
+/// [`Entry`]: enum.Entry.html
+pub struct OccupiedEntry<'a> {
+    occupied: OccupiedEntryImpl<'a>,
+}
+
+#[cfg(not(feature = "preserve_order"))]
+type VacantEntryImpl<'a> = btree_map::VacantEntry<'a, String, Value>;
+#[cfg(feature = "preserve_order")]
+type VacantEntryImpl<'a> = indexmap::map::VacantEntry<'a, String, Value>;
+
+#[cfg(not(feature = "preserve_order"))]
+type OccupiedEntryImpl<'a> = btree_map::OccupiedEntry<'a, String, Value>;
+#[cfg(feature = "preserve_order")]
+type OccupiedEntryImpl<'a> = indexmap::map::OccupiedEntry<'a, String, Value>;
+
+impl<'a> Entry<'a> {
+    /// Returns a reference to this entry's key.
+    pub fn key(&self) -> &String {
+        match *self {
+            Entry::Vacant(ref e) => e.key(),
+            Entry::Occupied(ref e) => e.key(),
+        }
+    }
+
+    /// Ensures a value is in the entry by inserting the default if empty, and
+    /// returns a mutable reference to the value in the entry.
+    pub fn or_insert(self, default: Value) -> &'a mut Value {
+        match self {
+            Entry::Vacant(entry) => entry.insert(default),
+            Entry::Occupied(entry) => entry.into_mut(),
+        }
+    }
+
+    /// Ensures a value is in the entry by inserting the result of the default
+    /// function if empty, and returns a mutable reference to the value in the
+    /// entry.
+    pub fn or_insert_with<F>(self, default: F) -> &'a mut Value
+    where
+        F: FnOnce() -> Value,
+    {
+        match self {
+            Entry::Vacant(entry) => entry.insert(default()),
+            Entry::Occupied(entry) => entry.into_mut(),
+        }
+    }
+}
+
+impl<'a> VacantEntry<'a> {
+    /// Gets a reference to the key that would be used when inserting a value
+    /// through the VacantEntry.
+    #[inline]
+    pub fn key(&self) -> &String {
+        self.vacant.key()
+    }
+
+    /// Sets the value of the entry with the VacantEntry's key, and returns a
+    /// mutable reference to it.
+    #[inline]
+    pub fn insert(self, value: Value) -> &'a mut Value {
+        self.vacant.insert(value)
+    }
+}
+
+impl<'a> OccupiedEntry<'a> {
+    /// Gets a reference to the key in the entry.
+    #[inline]
+    pub fn key(&self) -> &String {
+        self.occupied.key()
+    }
+
+    /// Gets a reference to the value in the entry.
+    #[inline]
+    pub fn get(&self) -> &Value {
+        self.occupied.get()
+    }
+
+    /// Gets a mutable reference to the value in the entry.
+    #[inline]
+    pub fn get_mut(&mut self) -> &mut Value {
+        self.occupied.get_mut()
+    }
+
+    /// Converts the entry into a mutable reference to its value.
+    #[inline]
+    pub fn into_mut(self) -> &'a mut Value {
+        self.occupied.into_mut()
+    }
+
+    /// Sets the value of the entry with the `OccupiedEntry`'s key, and returns
+    /// the entry's old value.
+    #[inline]
+    pub fn insert(&mut self, value: Value) -> Value {
+        self.occupied.insert(value)
+    }
+
+    /// Takes the value of the entry out of the map, and returns it.
+    #[inline]
+    pub fn remove(self) -> Value {
+        self.occupied.remove()
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+impl<'a> IntoIterator for &'a Map<String, Value> {
+    type Item = (&'a String, &'a Value);
+    type IntoIter = Iter<'a>;
+    #[inline]
+    fn into_iter(self) -> Self::IntoIter {
+        Iter {
+            iter: self.map.iter(),
+        }
+    }
+}
+
+/// An iterator over a toml::Map's entries.
+pub struct Iter<'a> {
+    iter: IterImpl<'a>,
+}
+
+#[cfg(not(feature = "preserve_order"))]
+type IterImpl<'a> = btree_map::Iter<'a, String, Value>;
+#[cfg(feature = "preserve_order")]
+type IterImpl<'a> = indexmap::map::Iter<'a, String, Value>;
+
+delegate_iterator!((Iter<'a>) => (&'a String, &'a Value));
+
+//////////////////////////////////////////////////////////////////////////////
+
+impl<'a> IntoIterator for &'a mut Map<String, Value> {
+    type Item = (&'a String, &'a mut Value);
+    type IntoIter = IterMut<'a>;
+    #[inline]
+    fn into_iter(self) -> Self::IntoIter {
+        IterMut {
+            iter: self.map.iter_mut(),
+        }
+    }
+}
+
+/// A mutable iterator over a toml::Map's entries.
+pub struct IterMut<'a> {
+    iter: IterMutImpl<'a>,
+}
+
+#[cfg(not(feature = "preserve_order"))]
+type IterMutImpl<'a> = btree_map::IterMut<'a, String, Value>;
+#[cfg(feature = "preserve_order")]
+type IterMutImpl<'a> = indexmap::map::IterMut<'a, String, Value>;
+
+delegate_iterator!((IterMut<'a>) => (&'a String, &'a mut Value));
+
+//////////////////////////////////////////////////////////////////////////////
+
+impl IntoIterator for Map<String, Value> {
+    type Item = (String, Value);
+    type IntoIter = IntoIter;
+    #[inline]
+    fn into_iter(self) -> Self::IntoIter {
+        IntoIter {
+            iter: self.map.into_iter(),
+        }
+    }
+}
+
+/// An owning iterator over a toml::Map's entries.
+pub struct IntoIter {
+    iter: IntoIterImpl,
+}
+
+#[cfg(not(feature = "preserve_order"))]
+type IntoIterImpl = btree_map::IntoIter<String, Value>;
+#[cfg(feature = "preserve_order")]
+type IntoIterImpl = indexmap::map::IntoIter<String, Value>;
+
+delegate_iterator!((IntoIter) => (String, Value));
+
+//////////////////////////////////////////////////////////////////////////////
+
+/// An iterator over a toml::Map's keys.
+pub struct Keys<'a> {
+    iter: KeysImpl<'a>,
+}
+
+#[cfg(not(feature = "preserve_order"))]
+type KeysImpl<'a> = btree_map::Keys<'a, String, Value>;
+#[cfg(feature = "preserve_order")]
+type KeysImpl<'a> = indexmap::map::Keys<'a, String, Value>;
+
+delegate_iterator!((Keys<'a>) => &'a String);
+
+//////////////////////////////////////////////////////////////////////////////
+
+/// An iterator over a toml::Map's values.
+pub struct Values<'a> {
+    iter: ValuesImpl<'a>,
+}
+
+#[cfg(not(feature = "preserve_order"))]
+type ValuesImpl<'a> = btree_map::Values<'a, String, Value>;
+#[cfg(feature = "preserve_order")]
+type ValuesImpl<'a> = indexmap::map::Values<'a, String, Value>;
+
+delegate_iterator!((Values<'a>) => &'a Value);
diff --git a/crates/toml/src/ser.rs b/crates/toml/src/ser.rs
new file mode 100644
index 00000000..2a13d4fb
--- /dev/null
+++ b/crates/toml/src/ser.rs
@@ -0,0 +1,1859 @@
+//! Serializing Rust structures into TOML.
+//!
+//! This module contains all the Serde support for serializing Rust structures
+//! into TOML documents (as strings). Note that some top-level functions here
+//! are also provided at the top of the crate.
+//!
+//! Note that the TOML format has a restriction that if a table itself contains
+//! tables, all keys with non-table values must be emitted first. This is
+//! typically easy to ensure happens when you're defining a `struct` as you can
+//! reorder the fields manually, but when working with maps (such as `BTreeMap`
+//! or `HashMap`) this can lead to serialization errors. In those situations you
+//! may use the `tables_last` function in this module like so:
+//!
+//! ```rust
+//! # use serde_derive::Serialize;
+//! # use std::collections::HashMap;
+//! #[derive(Serialize)]
+//! struct Manifest {
+//!     package: Package,
+//!     #[serde(serialize_with = "toml::ser::tables_last")]
+//!     dependencies: HashMap<String, Dependency>,
+//! }
+//! # type Package = String;
+//! # type Dependency = String;
+//! # fn main() {}
+//! ```
+
+use std::cell::Cell;
+use std::error;
+use std::fmt::{self, Write};
+use std::marker;
+use std::rc::Rc;
+
+use crate::datetime;
+use serde::ser;
+
+/// Serialize the given data structure as a TOML byte vector.
+///
+/// Serialization can fail if `T`'s implementation of `Serialize` decides to
+/// fail, if `T` contains a map with non-string keys, or if `T` attempts to
+/// serialize an unsupported datatype such as an enum, tuple, or tuple struct.
+pub fn to_vec<T: ?Sized>(value: &T) -> Result<Vec<u8>, Error>
+where
+    T: ser::Serialize,
+{
+    to_string(value).map(|e| e.into_bytes())
+}
+
+/// Serialize the given data structure as a String of TOML.
+///
+/// Serialization can fail if `T`'s implementation of `Serialize` decides to
+/// fail, if `T` contains a map with non-string keys, or if `T` attempts to
+/// serialize an unsupported datatype such as an enum, tuple, or tuple struct.
+///
+/// # Examples
+///
+/// ```
+/// use serde_derive::Serialize;
+///
+/// #[derive(Serialize)]
+/// struct Config {
+///     database: Database,
+/// }
+///
+/// #[derive(Serialize)]
+/// struct Database {
+///     ip: String,
+///     port: Vec<u16>,
+///     connection_max: u32,
+///     enabled: bool,
+/// }
+///
+/// fn main() {
+///     let config = Config {
+///         database: Database {
+///             ip: "192.168.1.1".to_string(),
+///             port: vec![8001, 8002, 8003],
+///             connection_max: 5000,
+///             enabled: false,
+///         },
+///     };
+///
+///     let toml = toml::to_string(&config).unwrap();
+///     println!("{}", toml)
+/// }
+/// ```
+pub fn to_string<T: ?Sized>(value: &T) -> Result<String, Error>
+where
+    T: ser::Serialize,
+{
+    let mut dst = String::with_capacity(128);
+    value.serialize(&mut Serializer::new(&mut dst))?;
+    Ok(dst)
+}
+
+/// Serialize the given data structure as a "pretty" String of TOML.
+///
+/// This is identical to `to_string` except the output string has a more
+/// "pretty" output. See `Serializer::pretty` for more details.
+pub fn to_string_pretty<T: ?Sized>(value: &T) -> Result<String, Error>
+where
+    T: ser::Serialize,
+{
+    let mut dst = String::with_capacity(128);
+    value.serialize(&mut Serializer::pretty(&mut dst))?;
+    Ok(dst)
+}
+
+/// Errors that can occur when serializing a type.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum Error {
+    /// Indicates that a Rust type was requested to be serialized but it was not
+    /// supported.
+    ///
+    /// Currently the TOML format does not support serializing types such as
+    /// enums, tuples and tuple structs.
+    UnsupportedType,
+
+    /// The key of all TOML maps must be strings, but serialization was
+    /// attempted where the key of a map was not a string.
+    KeyNotString,
+
+    /// An error that we never omit but keep for backwards compatibility
+    #[doc(hidden)]
+    KeyNewline,
+
+    /// An array had to be homogeneous, but now it is allowed to be heterogeneous.
+    #[doc(hidden)]
+    ArrayMixedType,
+
+    /// All values in a TOML table must be emitted before further tables are
+    /// emitted. If a value is emitted *after* a table then this error is
+    /// generated.
+    ValueAfterTable,
+
+    /// A serialized date was invalid.
+    DateInvalid,
+
+    /// A serialized number was invalid.
+    NumberInvalid,
+
+    /// None was attempted to be serialized, but it's not supported.
+    UnsupportedNone,
+
+    /// A custom error which could be generated when serializing a particular
+    /// type.
+    Custom(String),
+
+    #[doc(hidden)]
+    __Nonexhaustive,
+}
+
+#[derive(Debug, Default, Clone)]
+/// Internal place for holding array settings
+struct ArraySettings {
+    indent: usize,
+    trailing_comma: bool,
+}
+
+impl ArraySettings {
+    fn pretty() -> ArraySettings {
+        ArraySettings {
+            indent: 4,
+            trailing_comma: true,
+        }
+    }
+}
+
+#[derive(Debug, Default, Clone)]
+/// String settings
+struct StringSettings {
+    /// Whether to use literal strings when possible
+    literal: bool,
+}
+
+impl StringSettings {
+    fn pretty() -> StringSettings {
+        StringSettings { literal: true }
+    }
+}
+
+#[derive(Debug, Default, Clone)]
+/// Internal struct for holding serialization settings
+struct Settings {
+    array: Option<ArraySettings>,
+    string: Option<StringSettings>,
+}
+
+/// Serialization implementation for TOML.
+///
+/// This structure implements serialization support for TOML to serialize an
+/// arbitrary type to TOML. Note that the TOML format does not support all
+/// datatypes in Rust, such as enums, tuples, and tuple structs. These types
+/// will generate an error when serialized.
+///
+/// Currently a serializer always writes its output to an in-memory `String`,
+/// which is passed in when creating the serializer itself.
+pub struct Serializer<'a> {
+    dst: &'a mut String,
+    state: State<'a>,
+    settings: Rc<Settings>,
+}
+
+#[derive(Debug, Copy, Clone)]
+enum ArrayState {
+    Started,
+    StartedAsATable,
+}
+
+#[derive(Debug, Clone)]
+enum State<'a> {
+    Table {
+        key: &'a str,
+        parent: &'a State<'a>,
+        first: &'a Cell<bool>,
+        table_emitted: &'a Cell<bool>,
+    },
+    Array {
+        parent: &'a State<'a>,
+        first: &'a Cell<bool>,
+        type_: &'a Cell<Option<ArrayState>>,
+        len: Option<usize>,
+    },
+    End,
+}
+
+#[doc(hidden)]
+pub struct SerializeSeq<'a, 'b> {
+    ser: &'b mut Serializer<'a>,
+    first: Cell<bool>,
+    type_: Cell<Option<ArrayState>>,
+    len: Option<usize>,
+}
+
+#[doc(hidden)]
+pub enum SerializeTable<'a, 'b> {
+    Datetime(&'b mut Serializer<'a>),
+    Table {
+        ser: &'b mut Serializer<'a>,
+        key: String,
+        first: Cell<bool>,
+        table_emitted: Cell<bool>,
+    },
+}
+
+impl<'a> Serializer<'a> {
+    /// Creates a new serializer which will emit TOML into the buffer provided.
+    ///
+    /// The serializer can then be used to serialize a type after which the data
+    /// will be present in `dst`.
+    pub fn new(dst: &'a mut String) -> Serializer<'a> {
+        Serializer {
+            dst,
+            state: State::End,
+            settings: Rc::new(Settings::default()),
+        }
+    }
+
+    /// Instantiate a "pretty" formatter
+    ///
+    /// By default this will use:
+    ///
+    /// - pretty strings: strings with newlines will use the `'''` syntax. See
+    ///   `Serializer::pretty_string`
+    /// - pretty arrays: each item in arrays will be on a newline, have an indentation of 4 and
+    ///   have a trailing comma. See `Serializer::pretty_array`
+    pub fn pretty(dst: &'a mut String) -> Serializer<'a> {
+        Serializer {
+            dst,
+            state: State::End,
+            settings: Rc::new(Settings {
+                array: Some(ArraySettings::pretty()),
+                string: Some(StringSettings::pretty()),
+            }),
+        }
+    }
+
+    /// Enable or Disable pretty strings
+    ///
+    /// If enabled, literal strings will be used when possible and strings with
+    /// one or more newlines will use triple quotes (i.e.: `'''` or `"""`)
+    ///
+    /// # Examples
+    ///
+    /// Instead of:
+    ///
+    /// ```toml,ignore
+    /// single = "no newlines"
+    /// text = "\nfoo\nbar\n"
+    /// ```
+    ///
+    /// You will have:
+    ///
+    /// ```toml,ignore
+    /// single = 'no newlines'
+    /// text = '''
+    /// foo
+    /// bar
+    /// '''
+    /// ```
+    pub fn pretty_string(&mut self, value: bool) -> &mut Self {
+        Rc::get_mut(&mut self.settings).unwrap().string = if value {
+            Some(StringSettings::pretty())
+        } else {
+            None
+        };
+        self
+    }
+
+    /// Enable or Disable Literal strings for pretty strings
+    ///
+    /// If enabled, literal strings will be used when possible and strings with
+    /// one or more newlines will use triple quotes (i.e.: `'''` or `"""`)
+    ///
+    /// If disabled, literal strings will NEVER be used and strings with one or
+    /// more newlines will use `"""`
+    ///
+    /// # Examples
+    ///
+    /// Instead of:
+    ///
+    /// ```toml,ignore
+    /// single = "no newlines"
+    /// text = "\nfoo\nbar\n"
+    /// ```
+    ///
+    /// You will have:
+    ///
+    /// ```toml,ignore
+    /// single = "no newlines"
+    /// text = """
+    /// foo
+    /// bar
+    /// """
+    /// ```
+    pub fn pretty_string_literal(&mut self, value: bool) -> &mut Self {
+        let use_default = if let Some(ref mut s) = Rc::get_mut(&mut self.settings).unwrap().string {
+            s.literal = value;
+            false
+        } else {
+            true
+        };
+
+        if use_default {
+            let mut string = StringSettings::pretty();
+            string.literal = value;
+            Rc::get_mut(&mut self.settings).unwrap().string = Some(string);
+        }
+        self
+    }
+
+    /// Enable or Disable pretty arrays
+    ///
+    /// If enabled, arrays will always have each item on their own line.
+    ///
+    /// Some specific features can be controlled via other builder methods:
+    ///
+    /// - `Serializer::pretty_array_indent`: set the indent to a value other
+    ///   than 4.
+    /// - `Serializer::pretty_array_trailing_comma`: enable/disable the trailing
+    ///   comma on the last item.
+    ///
+    /// # Examples
+    ///
+    /// Instead of:
+    ///
+    /// ```toml,ignore
+    /// array = ["foo", "bar"]
+    /// ```
+    ///
+    /// You will have:
+    ///
+    /// ```toml,ignore
+    /// array = [
+    ///     "foo",
+    ///     "bar",
+    /// ]
+    /// ```
+    pub fn pretty_array(&mut self, value: bool) -> &mut Self {
+        Rc::get_mut(&mut self.settings).unwrap().array = if value {
+            Some(ArraySettings::pretty())
+        } else {
+            None
+        };
+        self
+    }
+
+    /// Set the indent for pretty arrays
+    ///
+    /// See `Serializer::pretty_array` for more details.
+    pub fn pretty_array_indent(&mut self, value: usize) -> &mut Self {
+        let use_default = if let Some(ref mut a) = Rc::get_mut(&mut self.settings).unwrap().array {
+            a.indent = value;
+            false
+        } else {
+            true
+        };
+
+        if use_default {
+            let mut array = ArraySettings::pretty();
+            array.indent = value;
+            Rc::get_mut(&mut self.settings).unwrap().array = Some(array);
+        }
+        self
+    }
+
+    /// Specify whether to use a trailing comma when serializing pretty arrays
+    ///
+    /// See `Serializer::pretty_array` for more details.
+    pub fn pretty_array_trailing_comma(&mut self, value: bool) -> &mut Self {
+        let use_default = if let Some(ref mut a) = Rc::get_mut(&mut self.settings).unwrap().array {
+            a.trailing_comma = value;
+            false
+        } else {
+            true
+        };
+
+        if use_default {
+            let mut array = ArraySettings::pretty();
+            array.trailing_comma = value;
+            Rc::get_mut(&mut self.settings).unwrap().array = Some(array);
+        }
+        self
+    }
+
+    fn display<T: fmt::Display>(&mut self, t: T, type_: ArrayState) -> Result<(), Error> {
+        self.emit_key(type_)?;
+        write!(self.dst, "{}", t).map_err(ser::Error::custom)?;
+        if let State::Table { .. } = self.state {
+            self.dst.push_str("\n");
+        }
+        Ok(())
+    }
+
+    fn emit_key(&mut self, type_: ArrayState) -> Result<(), Error> {
+        self.array_type(type_)?;
+        let state = self.state.clone();
+        self._emit_key(&state)
+    }
+
+    // recursive implementation of `emit_key` above
+    fn _emit_key(&mut self, state: &State<'_>) -> Result<(), Error> {
+        match *state {
+            State::End => Ok(()),
+            State::Array {
+                parent,
+                first,
+                type_,
+                len,
+            } => {
+                assert!(type_.get().is_some());
+                if first.get() {
+                    self._emit_key(parent)?;
+                }
+                self.emit_array(first, len)
+            }
+            State::Table {
+                parent,
+                first,
+                table_emitted,
+                key,
+            } => {
+                if table_emitted.get() {
+                    return Err(Error::ValueAfterTable);
+                }
+                if first.get() {
+                    self.emit_table_header(parent)?;
+                    first.set(false);
+                }
+                self.escape_key(key)?;
+                self.dst.push_str(" = ");
+                Ok(())
+            }
+        }
+    }
+
+    fn emit_array(&mut self, first: &Cell<bool>, len: Option<usize>) -> Result<(), Error> {
+        match (len, &self.settings.array) {
+            (Some(0..=1), _) | (_, &None) => {
+                if first.get() {
+                    self.dst.push_str("[")
+                } else {
+                    self.dst.push_str(", ")
+                }
+            }
+            (_, &Some(ref a)) => {
+                if first.get() {
+                    self.dst.push_str("[\n")
+                } else {
+                    self.dst.push_str(",\n")
+                }
+                for _ in 0..a.indent {
+                    self.dst.push_str(" ");
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn array_type(&mut self, type_: ArrayState) -> Result<(), Error> {
+        let prev = match self.state {
+            State::Array { type_, .. } => type_,
+            _ => return Ok(()),
+        };
+        if prev.get().is_none() {
+            prev.set(Some(type_));
+        }
+        Ok(())
+    }
+
+    fn escape_key(&mut self, key: &str) -> Result<(), Error> {
+        let ok = key.len() > 0
+            && key.chars().all(|c| match c {
+                'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true,
+                _ => false,
+            });
+        if ok {
+            write!(self.dst, "{}", key).map_err(ser::Error::custom)?;
+        } else {
+            self.emit_str(key, true)?;
+        }
+        Ok(())
+    }
+
+    fn emit_str(&mut self, value: &str, is_key: bool) -> Result<(), Error> {
+        #[derive(PartialEq)]
+        enum Type {
+            NewlineTripple,
+            OnelineTripple,
+            OnelineSingle,
+        }
+
+        enum Repr {
+            /// represent as a literal string (using '')
+            Literal(String, Type),
+            /// represent the std way (using "")
+            Std(Type),
+        }
+
+        fn do_pretty(value: &str) -> Repr {
+            // For doing pretty prints we store in a new String
+            // because there are too many cases where pretty cannot
+            // work. We need to determine:
+            // - if we are a "multi-line" pretty (if there are \n)
+            // - if ['''] appears if multi or ['] if single
+            // - if there are any invalid control characters
+            //
+            // Doing it any other way would require multiple passes
+            // to determine if a pretty string works or not.
+            let mut out = String::with_capacity(value.len() * 2);
+            let mut ty = Type::OnelineSingle;
+            // found consecutive single quotes
+            let mut max_found_singles = 0;
+            let mut found_singles = 0;
+            let mut can_be_pretty = true;
+
+            for ch in value.chars() {
+                if can_be_pretty {
+                    if ch == '\'' {
+                        found_singles += 1;
+                        if found_singles >= 3 {
+                            can_be_pretty = false;
+                        }
+                    } else {
+                        if found_singles > max_found_singles {
+                            max_found_singles = found_singles;
+                        }
+                        found_singles = 0
+                    }
+                    match ch {
+                        '\t' => {}
+                        '\n' => ty = Type::NewlineTripple,
+                        // Escape codes are needed if any ascii control
+                        // characters are present, including \b \f \r.
+                        c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false,
+                        _ => {}
+                    }
+                    out.push(ch);
+                } else {
+                    // the string cannot be represented as pretty,
+                    // still check if it should be multiline
+                    if ch == '\n' {
+                        ty = Type::NewlineTripple;
+                    }
+                }
+            }
+            if can_be_pretty && found_singles > 0 && value.ends_with('\'') {
+                // We cannot escape the ending quote so we must use """
+                can_be_pretty = false;
+            }
+            if !can_be_pretty {
+                debug_assert!(ty != Type::OnelineTripple);
+                return Repr::Std(ty);
+            }
+            if found_singles > max_found_singles {
+                max_found_singles = found_singles;
+            }
+            debug_assert!(max_found_singles < 3);
+            if ty == Type::OnelineSingle && max_found_singles >= 1 {
+                // no newlines, but must use ''' because it has ' in it
+                ty = Type::OnelineTripple;
+            }
+            Repr::Literal(out, ty)
+        }
+
+        let repr = if !is_key && self.settings.string.is_some() {
+            match (&self.settings.string, do_pretty(value)) {
+                (&Some(StringSettings { literal: false, .. }), Repr::Literal(_, ty)) => {
+                    Repr::Std(ty)
+                }
+                (_, r) => r,
+            }
+        } else {
+            Repr::Std(Type::OnelineSingle)
+        };
+        match repr {
+            Repr::Literal(literal, ty) => {
+                // A pretty string
+                match ty {
+                    Type::NewlineTripple => self.dst.push_str("'''\n"),
+                    Type::OnelineTripple => self.dst.push_str("'''"),
+                    Type::OnelineSingle => self.dst.push('\''),
+                }
+                self.dst.push_str(&literal);
+                match ty {
+                    Type::OnelineSingle => self.dst.push('\''),
+                    _ => self.dst.push_str("'''"),
+                }
+            }
+            Repr::Std(ty) => {
+                match ty {
+                    Type::NewlineTripple => self.dst.push_str("\"\"\"\n"),
+                    // note: OnelineTripple can happen if do_pretty wants to do
+                    // '''it's one line'''
+                    // but settings.string.literal == false
+                    Type::OnelineSingle | Type::OnelineTripple => self.dst.push('"'),
+                }
+                for ch in value.chars() {
+                    match ch {
+                        '\u{8}' => self.dst.push_str("\\b"),
+                        '\u{9}' => self.dst.push_str("\\t"),
+                        '\u{a}' => match ty {
+                            Type::NewlineTripple => self.dst.push('\n'),
+                            Type::OnelineSingle => self.dst.push_str("\\n"),
+                            _ => unreachable!(),
+                        },
+                        '\u{c}' => self.dst.push_str("\\f"),
+                        '\u{d}' => self.dst.push_str("\\r"),
+                        '\u{22}' => self.dst.push_str("\\\""),
+                        '\u{5c}' => self.dst.push_str("\\\\"),
+                        c if c <= '\u{1f}' || c == '\u{7f}' => {
+                            write!(self.dst, "\\u{:04X}", ch as u32).map_err(ser::Error::custom)?;
+                        }
+                        ch => self.dst.push(ch),
+                    }
+                }
+                match ty {
+                    Type::NewlineTripple => self.dst.push_str("\"\"\""),
+                    Type::OnelineSingle | Type::OnelineTripple => self.dst.push('"'),
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn emit_table_header(&mut self, state: &State<'_>) -> Result<(), Error> {
+        let array_of_tables = match *state {
+            State::End => return Ok(()),
+            State::Array { .. } => true,
+            _ => false,
+        };
+
+        // Unlike [..]s, we can't omit [[..]] ancestors, so be sure to emit table
+        // headers for them.
+        let mut p = state;
+        if let State::Array { first, parent, .. } = *state {
+            if first.get() {
+                p = parent;
+            }
+        }
+        while let State::Table { first, parent, .. } = *p {
+            p = parent;
+            if !first.get() {
+                break;
+            }
+            if let State::Array {
+                parent: &State::Table { .. },
+                ..
+            } = *parent
+            {
+                self.emit_table_header(parent)?;
+                break;
+            }
+        }
+
+        match *state {
+            State::Table { first, .. } => {
+                if !first.get() {
+                    // Newline if we are a table that is not the first
+                    // table in the document.
+                    self.dst.push('\n');
+                }
+            }
+            State::Array { parent, first, .. } => {
+                if !first.get() {
+                    // Always newline if we are not the first item in the
+                    // table-array
+                    self.dst.push('\n');
+                } else if let State::Table { first, .. } = *parent {
+                    if !first.get() {
+                        // Newline if we are not the first item in the document
+                        self.dst.push('\n');
+                    }
+                }
+            }
+            _ => {}
+        }
+        self.dst.push_str("[");
+        if array_of_tables {
+            self.dst.push_str("[");
+        }
+        self.emit_key_part(state)?;
+        if array_of_tables {
+            self.dst.push_str("]");
+        }
+        self.dst.push_str("]\n");
+        Ok(())
+    }
+
+    fn emit_key_part(&mut self, key: &State<'_>) -> Result<bool, Error> {
+        match *key {
+            State::Array { parent, .. } => self.emit_key_part(parent),
+            State::End => Ok(true),
+            State::Table {
+                key,
+                parent,
+                table_emitted,
+                ..
+            } => {
+                table_emitted.set(true);
+                let first = self.emit_key_part(parent)?;
+                if !first {
+                    self.dst.push_str(".");
+                }
+                self.escape_key(key)?;
+                Ok(false)
+            }
+        }
+    }
+}
+
+macro_rules! serialize_float {
+    ($this:expr, $v:expr) => {{
+        $this.emit_key(ArrayState::Started)?;
+        match ($v.is_sign_negative(), $v.is_nan(), $v == 0.0) {
+            (true, true, _) => write!($this.dst, "-nan"),
+            (false, true, _) => write!($this.dst, "nan"),
+            (true, false, true) => write!($this.dst, "-0.0"),
+            (false, false, true) => write!($this.dst, "0.0"),
+            (_, false, false) => write!($this.dst, "{}", $v).and_then(|_| {
+                if $v % 1.0 == 0.0 {
+                    write!($this.dst, ".0")
+                } else {
+                    Ok(())
+                }
+            }),
+        }
+        .map_err(ser::Error::custom)?;
+
+        if let State::Table { .. } = $this.state {
+            $this.dst.push_str("\n");
+        }
+        return Ok(());
+    }};
+}
+
+impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> {
+    type Ok = ();
+    type Error = Error;
+    type SerializeSeq = SerializeSeq<'a, 'b>;
+    type SerializeTuple = SerializeSeq<'a, 'b>;
+    type SerializeTupleStruct = SerializeSeq<'a, 'b>;
+    type SerializeTupleVariant = SerializeSeq<'a, 'b>;
+    type SerializeMap = SerializeTable<'a, 'b>;
+    type SerializeStruct = SerializeTable<'a, 'b>;
+    type SerializeStructVariant = ser::Impossible<(), Error>;
+
+    fn serialize_bool(self, v: bool) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_i8(self, v: i8) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_i16(self, v: i16) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_i32(self, v: i32) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_i64(self, v: i64) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_u8(self, v: u8) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_u16(self, v: u16) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_u32(self, v: u32) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_u64(self, v: u64) -> Result<(), Self::Error> {
+        self.display(v, ArrayState::Started)
+    }
+
+    fn serialize_f32(self, v: f32) -> Result<(), Self::Error> {
+        serialize_float!(self, v)
+    }
+
+    fn serialize_f64(self, v: f64) -> Result<(), Self::Error> {
+        serialize_float!(self, v)
+    }
+
+    fn serialize_char(self, v: char) -> Result<(), Self::Error> {
+        let mut buf = [0; 4];
+        self.serialize_str(v.encode_utf8(&mut buf))
+    }
+
+    fn serialize_str(self, value: &str) -> Result<(), Self::Error> {
+        self.emit_key(ArrayState::Started)?;
+        self.emit_str(value, false)?;
+        if let State::Table { .. } = self.state {
+            self.dst.push_str("\n");
+        }
+        Ok(())
+    }
+
+    fn serialize_bytes(self, value: &[u8]) -> Result<(), Self::Error> {
+        use serde::ser::Serialize;
+        value.serialize(self)
+    }
+
+    fn serialize_none(self) -> Result<(), Self::Error> {
+        Err(Error::UnsupportedNone)
+    }
+
+    fn serialize_some<T: ?Sized>(self, value: &T) -> Result<(), Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_unit(self) -> Result<(), Self::Error> {
+        Err(Error::UnsupportedType)
+    }
+
+    fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Self::Error> {
+        Err(Error::UnsupportedType)
+    }
+
+    fn serialize_unit_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        variant: &'static str,
+    ) -> Result<(), Self::Error> {
+        self.serialize_str(variant)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        _name: &'static str,
+        value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(Error::UnsupportedType)
+    }
+
+    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        self.array_type(ArrayState::Started)?;
+        Ok(SerializeSeq {
+            ser: self,
+            first: Cell::new(true),
+            type_: Cell::new(None),
+            len,
+        })
+    }
+
+    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        self.array_type(ArrayState::StartedAsATable)?;
+        Ok(SerializeTable::Table {
+            ser: self,
+            key: String::new(),
+            first: Cell::new(true),
+            table_emitted: Cell::new(false),
+        })
+    }
+
+    fn serialize_struct(
+        self,
+        name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStruct, Self::Error> {
+        if name == datetime::NAME {
+            self.array_type(ArrayState::Started)?;
+            Ok(SerializeTable::Datetime(self))
+        } else {
+            self.array_type(ArrayState::StartedAsATable)?;
+            Ok(SerializeTable::Table {
+                ser: self,
+                key: String::new(),
+                first: Cell::new(true),
+                table_emitted: Cell::new(false),
+            })
+        }
+    }
+
+    fn serialize_struct_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        Err(Error::UnsupportedType)
+    }
+}
+
+impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> {
+    type Ok = ();
+    type Error = Error;
+
+    fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(&mut Serializer {
+            dst: &mut *self.ser.dst,
+            state: State::Array {
+                parent: &self.ser.state,
+                first: &self.first,
+                type_: &self.type_,
+                len: self.len,
+            },
+            settings: self.ser.settings.clone(),
+        })?;
+        self.first.set(false);
+        Ok(())
+    }
+
+    fn end(self) -> Result<(), Error> {
+        match self.type_.get() {
+            Some(ArrayState::StartedAsATable) => return Ok(()),
+            Some(ArrayState::Started) => match (self.len, &self.ser.settings.array) {
+                (Some(0..=1), _) | (_, &None) => {
+                    self.ser.dst.push_str("]");
+                }
+                (_, &Some(ref a)) => {
+                    if a.trailing_comma {
+                        self.ser.dst.push_str(",");
+                    }
+                    self.ser.dst.push_str("\n]");
+                }
+            },
+            None => {
+                assert!(self.first.get());
+                self.ser.emit_key(ArrayState::Started)?;
+                self.ser.dst.push_str("[]")
+            }
+        }
+        if let State::Table { .. } = self.ser.state {
+            self.ser.dst.push_str("\n");
+        }
+        Ok(())
+    }
+}
+
+impl<'a, 'b> ser::SerializeTuple for SerializeSeq<'a, 'b> {
+    type Ok = ();
+    type Error = Error;
+
+    fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<(), Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+impl<'a, 'b> ser::SerializeTupleVariant for SerializeSeq<'a, 'b> {
+    type Ok = ();
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<(), Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+impl<'a, 'b> ser::SerializeTupleStruct for SerializeSeq<'a, 'b> {
+    type Ok = ();
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<(), Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+impl<'a, 'b> ser::SerializeMap for SerializeTable<'a, 'b> {
+    type Ok = ();
+    type Error = Error;
+
+    fn serialize_key<T: ?Sized>(&mut self, input: &T) -> Result<(), Error>
+    where
+        T: ser::Serialize,
+    {
+        match *self {
+            SerializeTable::Datetime(_) => panic!(), // shouldn't be possible
+            SerializeTable::Table { ref mut key, .. } => {
+                key.truncate(0);
+                *key = input.serialize(StringExtractor)?;
+            }
+        }
+        Ok(())
+    }
+
+    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), Error>
+    where
+        T: ser::Serialize,
+    {
+        match *self {
+            SerializeTable::Datetime(_) => panic!(), // shouldn't be possible
+            SerializeTable::Table {
+                ref mut ser,
+                ref key,
+                ref first,
+                ref table_emitted,
+                ..
+            } => {
+                let res = value.serialize(&mut Serializer {
+                    dst: &mut *ser.dst,
+                    state: State::Table {
+                        key,
+                        parent: &ser.state,
+                        first,
+                        table_emitted,
+                    },
+                    settings: ser.settings.clone(),
+                });
+                match res {
+                    Ok(()) => first.set(false),
+                    Err(Error::UnsupportedNone) => {}
+                    Err(e) => return Err(e),
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn end(self) -> Result<(), Error> {
+        match self {
+            SerializeTable::Datetime(_) => panic!(), // shouldn't be possible
+            SerializeTable::Table { ser, first, .. } => {
+                if first.get() {
+                    let state = ser.state.clone();
+                    ser.emit_table_header(&state)?;
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+impl<'a, 'b> ser::SerializeStruct for SerializeTable<'a, 'b> {
+    type Ok = ();
+    type Error = Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), Error>
+    where
+        T: ser::Serialize,
+    {
+        match *self {
+            SerializeTable::Datetime(ref mut ser) => {
+                if key == datetime::FIELD {
+                    value.serialize(DateStrEmitter(&mut *ser))?;
+                } else {
+                    return Err(Error::DateInvalid);
+                }
+            }
+            SerializeTable::Table {
+                ref mut ser,
+                ref first,
+                ref table_emitted,
+                ..
+            } => {
+                let res = value.serialize(&mut Serializer {
+                    dst: &mut *ser.dst,
+                    state: State::Table {
+                        key,
+                        parent: &ser.state,
+                        first,
+                        table_emitted,
+                    },
+                    settings: ser.settings.clone(),
+                });
+                match res {
+                    Ok(()) => first.set(false),
+                    Err(Error::UnsupportedNone) => {}
+                    Err(e) => return Err(e),
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn end(self) -> Result<(), Error> {
+        match self {
+            SerializeTable::Datetime(_) => {}
+            SerializeTable::Table { ser, first, .. } => {
+                if first.get() {
+                    let state = ser.state.clone();
+                    ser.emit_table_header(&state)?;
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+struct DateStrEmitter<'a, 'b>(&'b mut Serializer<'a>);
+
+impl<'a, 'b> ser::Serializer for DateStrEmitter<'a, 'b> {
+    type Ok = ();
+    type Error = Error;
+    type SerializeSeq = ser::Impossible<(), Error>;
+    type SerializeTuple = ser::Impossible<(), Error>;
+    type SerializeTupleStruct = ser::Impossible<(), Error>;
+    type SerializeTupleVariant = ser::Impossible<(), Error>;
+    type SerializeMap = ser::Impossible<(), Error>;
+    type SerializeStruct = ser::Impossible<(), Error>;
+    type SerializeStructVariant = ser::Impossible<(), Error>;
+
+    fn serialize_bool(self, _v: bool) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_i8(self, _v: i8) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_i16(self, _v: i16) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_i32(self, _v: i32) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_i64(self, _v: i64) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_u8(self, _v: u8) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_u16(self, _v: u16) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_u32(self, _v: u32) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_u64(self, _v: u64) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_f32(self, _v: f32) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_f64(self, _v: f64) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_char(self, _v: char) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_str(self, value: &str) -> Result<(), Self::Error> {
+        self.0.display(value, ArrayState::Started)?;
+        Ok(())
+    }
+
+    fn serialize_bytes(self, _value: &[u8]) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_none(self) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_some<T: ?Sized>(self, _value: &T) -> Result<(), Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_unit(self) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_unit_struct(self, _name: &'static str) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_unit_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+    ) -> Result<(), Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        _name: &'static str,
+        _value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _value: &T,
+    ) -> Result<(), Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_struct(
+        self,
+        _name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStruct, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+
+    fn serialize_struct_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        Err(Error::DateInvalid)
+    }
+}
+
+struct StringExtractor;
+
+impl ser::Serializer for StringExtractor {
+    type Ok = String;
+    type Error = Error;
+    type SerializeSeq = ser::Impossible<String, Error>;
+    type SerializeTuple = ser::Impossible<String, Error>;
+    type SerializeTupleStruct = ser::Impossible<String, Error>;
+    type SerializeTupleVariant = ser::Impossible<String, Error>;
+    type SerializeMap = ser::Impossible<String, Error>;
+    type SerializeStruct = ser::Impossible<String, Error>;
+    type SerializeStructVariant = ser::Impossible<String, Error>;
+
+    fn serialize_bool(self, _v: bool) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_i8(self, _v: i8) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_i16(self, _v: i16) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_i32(self, _v: i32) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_i64(self, _v: i64) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_u8(self, _v: u8) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_u16(self, _v: u16) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_u32(self, _v: u32) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_u64(self, _v: u64) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_f32(self, _v: f32) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_f64(self, _v: f64) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_char(self, _v: char) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_str(self, value: &str) -> Result<String, Self::Error> {
+        Ok(value.to_string())
+    }
+
+    fn serialize_bytes(self, _value: &[u8]) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_none(self) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_some<T: ?Sized>(self, _value: &T) -> Result<String, Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_unit(self) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_unit_struct(self, _name: &'static str) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_unit_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+    ) -> Result<String, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        _name: &'static str,
+        value: &T,
+    ) -> Result<String, Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _value: &T,
+    ) -> Result<String, Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_struct(
+        self,
+        _name: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStruct, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+
+    fn serialize_struct_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        Err(Error::KeyNotString)
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match *self {
+            Error::UnsupportedType => "unsupported Rust type".fmt(f),
+            Error::KeyNotString => "map key was not a string".fmt(f),
+            Error::ValueAfterTable => "values must be emitted before tables".fmt(f),
+            Error::DateInvalid => "a serialized date was invalid".fmt(f),
+            Error::NumberInvalid => "a serialized number was invalid".fmt(f),
+            Error::UnsupportedNone => "unsupported None value".fmt(f),
+            Error::Custom(ref s) => s.fmt(f),
+            Error::KeyNewline => unreachable!(),
+            Error::ArrayMixedType => unreachable!(),
+            Error::__Nonexhaustive => panic!(),
+        }
+    }
+}
+
+impl error::Error for Error {}
+
+impl ser::Error for Error {
+    fn custom<T: fmt::Display>(msg: T) -> Error {
+        Error::Custom(msg.to_string())
+    }
+}
+
+enum Category {
+    Primitive,
+    Array,
+    Table,
+}
+
+/// Convenience function to serialize items in a map in an order valid with
+/// TOML.
+///
+/// TOML carries the restriction that keys in a table must be serialized last if
+/// their value is a table itself. This isn't always easy to guarantee, so this
+/// helper can be used like so:
+///
+/// ```rust
+/// # use serde_derive::Serialize;
+/// # use std::collections::HashMap;
+/// #[derive(Serialize)]
+/// struct Manifest {
+///     package: Package,
+///     #[serde(serialize_with = "toml::ser::tables_last")]
+///     dependencies: HashMap<String, Dependency>,
+/// }
+/// # type Package = String;
+/// # type Dependency = String;
+/// # fn main() {}
+/// ```
+pub fn tables_last<'a, I, K, V, S>(data: &'a I, serializer: S) -> Result<S::Ok, S::Error>
+where
+    &'a I: IntoIterator<Item = (K, V)>,
+    K: ser::Serialize,
+    V: ser::Serialize,
+    S: ser::Serializer,
+{
+    use serde::ser::SerializeMap;
+
+    let mut map = serializer.serialize_map(None)?;
+    for (k, v) in data {
+        if let Category::Primitive = v.serialize(Categorize::new())? {
+            map.serialize_entry(&k, &v)?;
+        }
+    }
+    for (k, v) in data {
+        if let Category::Array = v.serialize(Categorize::new())? {
+            map.serialize_entry(&k, &v)?;
+        }
+    }
+    for (k, v) in data {
+        if let Category::Table = v.serialize(Categorize::new())? {
+            map.serialize_entry(&k, &v)?;
+        }
+    }
+    map.end()
+}
+
+struct Categorize<E>(marker::PhantomData<E>);
+
+impl<E> Categorize<E> {
+    fn new() -> Self {
+        Categorize(marker::PhantomData)
+    }
+}
+
+impl<E: ser::Error> ser::Serializer for Categorize<E> {
+    type Ok = Category;
+    type Error = E;
+    type SerializeSeq = Self;
+    type SerializeTuple = Self;
+    type SerializeTupleStruct = Self;
+    type SerializeTupleVariant = Self;
+    type SerializeMap = Self;
+    type SerializeStruct = Self;
+    type SerializeStructVariant = ser::Impossible<Category, E>;
+
+    fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_i8(self, _: i8) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_i16(self, _: i16) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_i32(self, _: i32) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_i64(self, _: i64) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_u8(self, _: u8) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_u16(self, _: u16) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_u32(self, _: u32) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_u64(self, _: u64) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_f32(self, _: f32) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_f64(self, _: f64) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_char(self, _: char) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_str(self, _: &str) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Primitive)
+    }
+
+    fn serialize_bytes(self, _: &[u8]) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Array)
+    }
+
+    fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
+        Err(ser::Error::custom("unsupported"))
+    }
+
+    fn serialize_some<T: ?Sized + ser::Serialize>(self, v: &T) -> Result<Self::Ok, Self::Error> {
+        v.serialize(self)
+    }
+
+    fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
+        Err(ser::Error::custom("unsupported"))
+    }
+
+    fn serialize_unit_struct(self, _: &'static str) -> Result<Self::Ok, Self::Error> {
+        Err(ser::Error::custom("unsupported"))
+    }
+
+    fn serialize_unit_variant(
+        self,
+        _: &'static str,
+        _: u32,
+        _: &'static str,
+    ) -> Result<Self::Ok, Self::Error> {
+        Err(ser::Error::custom("unsupported"))
+    }
+
+    fn serialize_newtype_struct<T: ?Sized + ser::Serialize>(
+        self,
+        _: &'static str,
+        v: &T,
+    ) -> Result<Self::Ok, Self::Error> {
+        v.serialize(self)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized + ser::Serialize>(
+        self,
+        _: &'static str,
+        _: u32,
+        _: &'static str,
+        _: &T,
+    ) -> Result<Self::Ok, Self::Error> {
+        Err(ser::Error::custom("unsupported"))
+    }
+
+    fn serialize_seq(self, _: Option<usize>) -> Result<Self, Self::Error> {
+        Ok(self)
+    }
+
+    fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
+        Ok(self)
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _: &'static str,
+        _: usize,
+    ) -> Result<Self::SerializeTupleStruct, Self::Error> {
+        Ok(self)
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _: &'static str,
+        _: u32,
+        _: &'static str,
+        _: usize,
+    ) -> Result<Self::SerializeTupleVariant, Self::Error> {
+        Ok(self)
+    }
+
+    fn serialize_map(self, _: Option<usize>) -> Result<Self, Self::Error> {
+        Ok(self)
+    }
+
+    fn serialize_struct(self, _: &'static str, _: usize) -> Result<Self, Self::Error> {
+        Ok(self)
+    }
+
+    fn serialize_struct_variant(
+        self,
+        _: &'static str,
+        _: u32,
+        _: &'static str,
+        _: usize,
+    ) -> Result<Self::SerializeStructVariant, Self::Error> {
+        Err(ser::Error::custom("unsupported"))
+    }
+}
+
+impl<E: ser::Error> ser::SerializeSeq for Categorize<E> {
+    type Ok = Category;
+    type Error = E;
+
+    fn serialize_element<T: ?Sized + ser::Serialize>(&mut self, _: &T) -> Result<(), Self::Error> {
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Array)
+    }
+}
+
+impl<E: ser::Error> ser::SerializeTuple for Categorize<E> {
+    type Ok = Category;
+    type Error = E;
+
+    fn serialize_element<T: ?Sized + ser::Serialize>(&mut self, _: &T) -> Result<(), Self::Error> {
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Array)
+    }
+}
+
+impl<E: ser::Error> ser::SerializeTupleVariant for Categorize<E> {
+    type Ok = Category;
+    type Error = E;
+
+    fn serialize_field<T: ?Sized + ser::Serialize>(&mut self, _: &T) -> Result<(), Self::Error> {
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Array)
+    }
+}
+
+impl<E: ser::Error> ser::SerializeTupleStruct for Categorize<E> {
+    type Ok = Category;
+    type Error = E;
+
+    fn serialize_field<T: ?Sized + ser::Serialize>(&mut self, _: &T) -> Result<(), Self::Error> {
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Array)
+    }
+}
+
+impl<E: ser::Error> ser::SerializeMap for Categorize<E> {
+    type Ok = Category;
+    type Error = E;
+
+    fn serialize_key<T: ?Sized + ser::Serialize>(&mut self, _: &T) -> Result<(), Self::Error> {
+        Ok(())
+    }
+
+    fn serialize_value<T: ?Sized + ser::Serialize>(&mut self, _: &T) -> Result<(), Self::Error> {
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Table)
+    }
+}
+
+impl<E: ser::Error> ser::SerializeStruct for Categorize<E> {
+    type Ok = Category;
+    type Error = E;
+
+    fn serialize_field<T: ?Sized>(&mut self, _: &'static str, _: &T) -> Result<(), Self::Error>
+    where
+        T: ser::Serialize,
+    {
+        Ok(())
+    }
+
+    fn end(self) -> Result<Self::Ok, Self::Error> {
+        Ok(Category::Table)
+    }
+}
diff --git a/crates/toml/src/spanned.rs b/crates/toml/src/spanned.rs
new file mode 100644
index 00000000..9ee56ae6
--- /dev/null
+++ b/crates/toml/src/spanned.rs
@@ -0,0 +1,168 @@
+use serde::{de, ser};
+use std::borrow::Borrow;
+use std::cmp::Ordering;
+use std::fmt;
+use std::hash::{Hash, Hasher};
+
+pub(crate) const NAME: &str = "$__toml_private_Spanned";
+pub(crate) const START: &str = "$__toml_private_start";
+pub(crate) const END: &str = "$__toml_private_end";
+pub(crate) const VALUE: &str = "$__toml_private_value";
+
+/// A spanned value, indicating the range at which it is defined in the source.
+///
+/// ```
+/// use serde_derive::Deserialize;
+/// use toml::Spanned;
+///
+/// #[derive(Deserialize)]
+/// struct Value {
+///     s: Spanned<String>,
+/// }
+///
+/// fn main() {
+///     let t = "s = \"value\"\n";
+///
+///     let u: Value = toml::from_str(t).unwrap();
+///
+///     assert_eq!(u.s.start(), 4);
+///     assert_eq!(u.s.end(), 11);
+///     assert_eq!(u.s.get_ref(), "value");
+///     assert_eq!(u.s.into_inner(), String::from("value"));
+/// }
+/// ```
+#[derive(Clone, Debug)]
+pub struct Spanned<T> {
+    /// The start range.
+    start: usize,
+    /// The end range (exclusive).
+    end: usize,
+    /// The spanned value.
+    value: T,
+}
+
+impl<T> Spanned<T> {
+    /// Access the start of the span of the contained value.
+    pub fn start(&self) -> usize {
+        self.start
+    }
+
+    /// Access the end of the span of the contained value.
+    pub fn end(&self) -> usize {
+        self.end
+    }
+
+    /// Get the span of the contained value.
+    pub fn span(&self) -> (usize, usize) {
+        (self.start, self.end)
+    }
+
+    /// Consumes the spanned value and returns the contained value.
+    pub fn into_inner(self) -> T {
+        self.value
+    }
+
+    /// Returns a reference to the contained value.
+    pub fn get_ref(&self) -> &T {
+        &self.value
+    }
+
+    /// Returns a mutable reference to the contained value.
+    pub fn get_mut(&mut self) -> &mut T {
+        &mut self.value
+    }
+}
+
+impl Borrow<str> for Spanned<String> {
+    fn borrow(&self) -> &str {
+        &self.get_ref()
+    }
+}
+
+impl<T: PartialEq> PartialEq for Spanned<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.value.eq(&other.value)
+    }
+}
+
+impl<T: Eq> Eq for Spanned<T> {}
+
+impl<T: Hash> Hash for Spanned<T> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.value.hash(state);
+    }
+}
+
+impl<T: PartialOrd> PartialOrd for Spanned<T> {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.value.partial_cmp(&other.value)
+    }
+}
+
+impl<T: Ord> Ord for Spanned<T> {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.value.cmp(&other.value)
+    }
+}
+
+impl<'de, T> de::Deserialize<'de> for Spanned<T>
+where
+    T: de::Deserialize<'de>,
+{
+    fn deserialize<D>(deserializer: D) -> Result<Spanned<T>, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        struct SpannedVisitor<T>(::std::marker::PhantomData<T>);
+
+        impl<'de, T> de::Visitor<'de> for SpannedVisitor<T>
+        where
+            T: de::Deserialize<'de>,
+        {
+            type Value = Spanned<T>;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                formatter.write_str("a TOML spanned")
+            }
+
+            fn visit_map<V>(self, mut visitor: V) -> Result<Spanned<T>, V::Error>
+            where
+                V: de::MapAccess<'de>,
+            {
+                if visitor.next_key()? != Some(START) {
+                    return Err(de::Error::custom("spanned start key not found"));
+                }
+
+                let start: usize = visitor.next_value()?;
+
+                if visitor.next_key()? != Some(END) {
+                    return Err(de::Error::custom("spanned end key not found"));
+                }
+
+                let end: usize = visitor.next_value()?;
+
+                if visitor.next_key()? != Some(VALUE) {
+                    return Err(de::Error::custom("spanned value key not found"));
+                }
+
+                let value: T = visitor.next_value()?;
+
+                Ok(Spanned { start, end, value })
+            }
+        }
+
+        let visitor = SpannedVisitor(::std::marker::PhantomData);
+
+        static FIELDS: [&str; 3] = [START, END, VALUE];
+        deserializer.deserialize_struct(NAME, &FIELDS, visitor)
+    }
+}
+
+impl<T: ser::Serialize> ser::Serialize for Spanned<T> {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        self.value.serialize(serializer)
+    }
+}
diff --git a/crates/toml/src/tokens.rs b/crates/toml/src/tokens.rs
new file mode 100644
index 00000000..c2422828
--- /dev/null
+++ b/crates/toml/src/tokens.rs
@@ -0,0 +1,740 @@
+use std::borrow::Cow;
+use std::char;
+use std::str;
+use std::string;
+use std::string::String as StdString;
+
+use self::Token::*;
+
+/// A span, designating a range of bytes where a token is located.
+#[derive(Eq, PartialEq, Debug, Clone, Copy)]
+pub struct Span {
+    /// The start of the range.
+    pub start: usize,
+    /// The end of the range (exclusive).
+    pub end: usize,
+}
+
+impl From<Span> for (usize, usize) {
+    fn from(Span { start, end }: Span) -> (usize, usize) {
+        (start, end)
+    }
+}
+
+#[derive(Eq, PartialEq, Debug)]
+pub enum Token<'a> {
+    Whitespace(&'a str),
+    Newline,
+    Comment(&'a str),
+
+    Equals,
+    Period,
+    Comma,
+    Colon,
+    Plus,
+    LeftBrace,
+    RightBrace,
+    LeftBracket,
+    RightBracket,
+
+    Keylike(&'a str),
+    String {
+        src: &'a str,
+        val: Cow<'a, str>,
+        multiline: bool,
+    },
+}
+
+#[derive(Eq, PartialEq, Debug)]
+pub enum Error {
+    InvalidCharInString(usize, char),
+    InvalidEscape(usize, char),
+    InvalidHexEscape(usize, char),
+    InvalidEscapeValue(usize, u32),
+    NewlineInString(usize),
+    Unexpected(usize, char),
+    UnterminatedString(usize),
+    NewlineInTableKey(usize),
+    MultilineStringKey(usize),
+    Wanted {
+        at: usize,
+        expected: &'static str,
+        found: &'static str,
+    },
+}
+
+#[derive(Clone)]
+pub struct Tokenizer<'a> {
+    input: &'a str,
+    chars: CrlfFold<'a>,
+}
+
+#[derive(Clone)]
+struct CrlfFold<'a> {
+    chars: str::CharIndices<'a>,
+}
+
+#[derive(Debug)]
+enum MaybeString {
+    NotEscaped(usize),
+    Owned(string::String),
+}
+
+impl<'a> Tokenizer<'a> {
+    pub fn new(input: &'a str) -> Tokenizer<'a> {
+        let mut t = Tokenizer {
+            input,
+            chars: CrlfFold {
+                chars: input.char_indices(),
+            },
+        };
+        // Eat utf-8 BOM
+        t.eatc('\u{feff}');
+        t
+    }
+
+    pub fn next(&mut self) -> Result<Option<(Span, Token<'a>)>, Error> {
+        let (start, token) = match self.one() {
+            Some((start, '\n')) => (start, Newline),
+            Some((start, ' ')) => (start, self.whitespace_token(start)),
+            Some((start, '\t')) => (start, self.whitespace_token(start)),
+            Some((start, '#')) => (start, self.comment_token(start)),
+            Some((start, '=')) => (start, Equals),
+            Some((start, '.')) => (start, Period),
+            Some((start, ',')) => (start, Comma),
+            Some((start, ':')) => (start, Colon),
+            Some((start, '+')) => (start, Plus),
+            Some((start, '{')) => (start, LeftBrace),
+            Some((start, '}')) => (start, RightBrace),
+            Some((start, '[')) => (start, LeftBracket),
+            Some((start, ']')) => (start, RightBracket),
+            Some((start, '\'')) => {
+                return self
+                    .literal_string(start)
+                    .map(|t| Some((self.step_span(start), t)))
+            }
+            Some((start, '"')) => {
+                return self
+                    .basic_string(start)
+                    .map(|t| Some((self.step_span(start), t)))
+            }
+            Some((start, ch)) if is_keylike(ch) => (start, self.keylike(start)),
+
+            Some((start, ch)) => return Err(Error::Unexpected(start, ch)),
+            None => return Ok(None),
+        };
+
+        let span = self.step_span(start);
+        Ok(Some((span, token)))
+    }
+
+    pub fn peek(&mut self) -> Result<Option<(Span, Token<'a>)>, Error> {
+        self.clone().next()
+    }
+
+    pub fn eat(&mut self, expected: Token<'a>) -> Result<bool, Error> {
+        self.eat_spanned(expected).map(|s| s.is_some())
+    }
+
+    /// Eat a value, returning it's span if it was consumed.
+    pub fn eat_spanned(&mut self, expected: Token<'a>) -> Result<Option<Span>, Error> {
+        let span = match self.peek()? {
+            Some((span, ref found)) if expected == *found => span,
+            Some(_) => return Ok(None),
+            None => return Ok(None),
+        };
+
+        drop(self.next());
+        Ok(Some(span))
+    }
+
+    pub fn expect(&mut self, expected: Token<'a>) -> Result<(), Error> {
+        // ignore span
+        let _ = self.expect_spanned(expected)?;
+        Ok(())
+    }
+
+    /// Expect the given token returning its span.
+    pub fn expect_spanned(&mut self, expected: Token<'a>) -> Result<Span, Error> {
+        let current = self.current();
+        match self.next()? {
+            Some((span, found)) => {
+                if expected == found {
+                    Ok(span)
+                } else {
+                    Err(Error::Wanted {
+                        at: current,
+                        expected: expected.describe(),
+                        found: found.describe(),
+                    })
+                }
+            }
+            None => Err(Error::Wanted {
+                at: self.input.len(),
+                expected: expected.describe(),
+                found: "eof",
+            }),
+        }
+    }
+
+    pub fn table_key(&mut self) -> Result<(Span, Cow<'a, str>), Error> {
+        let current = self.current();
+        match self.next()? {
+            Some((span, Token::Keylike(k))) => Ok((span, k.into())),
+            Some((
+                span,
+                Token::String {
+                    src,
+                    val,
+                    multiline,
+                },
+            )) => {
+                let offset = self.substr_offset(src);
+                if multiline {
+                    return Err(Error::MultilineStringKey(offset));
+                }
+                match src.find('\n') {
+                    None => Ok((span, val)),
+                    Some(i) => Err(Error::NewlineInTableKey(offset + i)),
+                }
+            }
+            Some((_, other)) => Err(Error::Wanted {
+                at: current,
+                expected: "a table key",
+                found: other.describe(),
+            }),
+            None => Err(Error::Wanted {
+                at: self.input.len(),
+                expected: "a table key",
+                found: "eof",
+            }),
+        }
+    }
+
+    pub fn eat_whitespace(&mut self) -> Result<(), Error> {
+        while self.eatc(' ') || self.eatc('\t') {
+            // ...
+        }
+        Ok(())
+    }
+
+    pub fn eat_comment(&mut self) -> Result<bool, Error> {
+        if !self.eatc('#') {
+            return Ok(false);
+        }
+        drop(self.comment_token(0));
+        self.eat_newline_or_eof().map(|()| true)
+    }
+
+    pub fn eat_newline_or_eof(&mut self) -> Result<(), Error> {
+        let current = self.current();
+        match self.next()? {
+            None | Some((_, Token::Newline)) => Ok(()),
+            Some((_, other)) => Err(Error::Wanted {
+                at: current,
+                expected: "newline",
+                found: other.describe(),
+            }),
+        }
+    }
+
+    pub fn skip_to_newline(&mut self) {
+        loop {
+            match self.one() {
+                Some((_, '\n')) | None => break,
+                _ => {}
+            }
+        }
+    }
+
+    fn eatc(&mut self, ch: char) -> bool {
+        match self.chars.clone().next() {
+            Some((_, ch2)) if ch == ch2 => {
+                self.one();
+                true
+            }
+            _ => false,
+        }
+    }
+
+    pub fn current(&mut self) -> usize {
+        self.chars
+            .clone()
+            .next()
+            .map(|i| i.0)
+            .unwrap_or_else(|| self.input.len())
+    }
+
+    pub fn input(&self) -> &'a str {
+        self.input
+    }
+
+    fn whitespace_token(&mut self, start: usize) -> Token<'a> {
+        while self.eatc(' ') || self.eatc('\t') {
+            // ...
+        }
+        Whitespace(&self.input[start..self.current()])
+    }
+
+    fn comment_token(&mut self, start: usize) -> Token<'a> {
+        while let Some((_, ch)) = self.chars.clone().next() {
+            if ch != '\t' && (ch < '\u{20}' || ch > '\u{10ffff}') {
+                break;
+            }
+            self.one();
+        }
+        Comment(&self.input[start..self.current()])
+    }
+
+    fn read_string(
+        &mut self,
+        delim: char,
+        start: usize,
+        new_ch: &mut dyn FnMut(
+            &mut Tokenizer<'_>,
+            &mut MaybeString,
+            bool,
+            usize,
+            char,
+        ) -> Result<(), Error>,
+    ) -> Result<Token<'a>, Error> {
+        let mut multiline = false;
+        if self.eatc(delim) {
+            if self.eatc(delim) {
+                multiline = true;
+            } else {
+                return Ok(String {
+                    src: &self.input[start..start + 2],
+                    val: Cow::Borrowed(""),
+                    multiline: false,
+                });
+            }
+        }
+        let mut val = MaybeString::NotEscaped(self.current());
+        let mut n = 0;
+        'outer: loop {
+            n += 1;
+            match self.one() {
+                Some((i, '\n')) => {
+                    if multiline {
+                        if self.input.as_bytes()[i] == b'\r' {
+                            val.to_owned(&self.input[..i]);
+                        }
+                        if n == 1 {
+                            val = MaybeString::NotEscaped(self.current());
+                        } else {
+                            val.push('\n');
+                        }
+                        continue;
+                    } else {
+                        return Err(Error::NewlineInString(i));
+                    }
+                }
+                Some((mut i, ch)) if ch == delim => {
+                    if multiline {
+                        if !self.eatc(delim) {
+                            val.push(delim);
+                            continue 'outer;
+                        }
+                        if !self.eatc(delim) {
+                            val.push(delim);
+                            val.push(delim);
+                            continue 'outer;
+                        }
+                        if self.eatc(delim) {
+                            val.push(delim);
+                            i += 1;
+                        }
+                        if self.eatc(delim) {
+                            val.push(delim);
+                            i += 1;
+                        }
+                    }
+                    return Ok(String {
+                        src: &self.input[start..self.current()],
+                        val: val.into_cow(&self.input[..i]),
+                        multiline,
+                    });
+                }
+                Some((i, c)) => new_ch(self, &mut val, multiline, i, c)?,
+                None => return Err(Error::UnterminatedString(start)),
+            }
+        }
+    }
+
+    fn literal_string(&mut self, start: usize) -> Result<Token<'a>, Error> {
+        self.read_string('\'', start, &mut |_me, val, _multi, i, ch| {
+            if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') {
+                val.push(ch);
+                Ok(())
+            } else {
+                Err(Error::InvalidCharInString(i, ch))
+            }
+        })
+    }
+
+    fn basic_string(&mut self, start: usize) -> Result<Token<'a>, Error> {
+        self.read_string('"', start, &mut |me, val, multi, i, ch| match ch {
+            '\\' => {
+                val.to_owned(&me.input[..i]);
+                match me.chars.next() {
+                    Some((_, '"')) => val.push('"'),
+                    Some((_, '\\')) => val.push('\\'),
+                    Some((_, 'b')) => val.push('\u{8}'),
+                    Some((_, 'f')) => val.push('\u{c}'),
+                    Some((_, 'n')) => val.push('\n'),
+                    Some((_, 'r')) => val.push('\r'),
+                    Some((_, 't')) => val.push('\t'),
+                    Some((i, c @ 'u')) | Some((i, c @ 'U')) => {
+                        let len = if c == 'u' { 4 } else { 8 };
+                        val.push(me.hex(start, i, len)?);
+                    }
+                    Some((i, c @ ' ')) | Some((i, c @ '\t')) | Some((i, c @ '\n')) if multi => {
+                        if c != '\n' {
+                            while let Some((_, ch)) = me.chars.clone().next() {
+                                match ch {
+                                    ' ' | '\t' => {
+                                        me.chars.next();
+                                        continue;
+                                    }
+                                    '\n' => {
+                                        me.chars.next();
+                                        break;
+                                    }
+                                    _ => return Err(Error::InvalidEscape(i, c)),
+                                }
+                            }
+                        }
+                        while let Some((_, ch)) = me.chars.clone().next() {
+                            match ch {
+                                ' ' | '\t' | '\n' => {
+                                    me.chars.next();
+                                }
+                                _ => break,
+                            }
+                        }
+                    }
+                    Some((i, c)) => return Err(Error::InvalidEscape(i, c)),
+                    None => return Err(Error::UnterminatedString(start)),
+                }
+                Ok(())
+            }
+            ch if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') => {
+                val.push(ch);
+                Ok(())
+            }
+            _ => Err(Error::InvalidCharInString(i, ch)),
+        })
+    }
+
+    fn hex(&mut self, start: usize, i: usize, len: usize) -> Result<char, Error> {
+        let mut buf = StdString::with_capacity(len);
+        for _ in 0..len {
+            match self.one() {
+                Some((_, ch)) if ch as u32 <= 0x7F && ch.is_digit(16) => buf.push(ch),
+                Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)),
+                None => return Err(Error::UnterminatedString(start)),
+            }
+        }
+        let val = u32::from_str_radix(&buf, 16).unwrap();
+        match char::from_u32(val) {
+            Some(ch) => Ok(ch),
+            None => Err(Error::InvalidEscapeValue(i, val)),
+        }
+    }
+
+    fn keylike(&mut self, start: usize) -> Token<'a> {
+        while let Some((_, ch)) = self.peek_one() {
+            if !is_keylike(ch) {
+                break;
+            }
+            self.one();
+        }
+        Keylike(&self.input[start..self.current()])
+    }
+
+    pub fn substr_offset(&self, s: &'a str) -> usize {
+        assert!(s.len() <= self.input.len());
+        let a = self.input.as_ptr() as usize;
+        let b = s.as_ptr() as usize;
+        assert!(a <= b);
+        b - a
+    }
+
+    /// Calculate the span of a single character.
+    fn step_span(&mut self, start: usize) -> Span {
+        let end = self
+            .peek_one()
+            .map(|t| t.0)
+            .unwrap_or_else(|| self.input.len());
+        Span { start, end }
+    }
+
+    /// Peek one char without consuming it.
+    fn peek_one(&mut self) -> Option<(usize, char)> {
+        self.chars.clone().next()
+    }
+
+    /// Take one char.
+    pub fn one(&mut self) -> Option<(usize, char)> {
+        self.chars.next()
+    }
+}
+
+impl<'a> Iterator for CrlfFold<'a> {
+    type Item = (usize, char);
+
+    fn next(&mut self) -> Option<(usize, char)> {
+        self.chars.next().map(|(i, c)| {
+            if c == '\r' {
+                let mut attempt = self.chars.clone();
+                if let Some((_, '\n')) = attempt.next() {
+                    self.chars = attempt;
+                    return (i, '\n');
+                }
+            }
+            (i, c)
+        })
+    }
+}
+
+impl MaybeString {
+    fn push(&mut self, ch: char) {
+        match *self {
+            MaybeString::NotEscaped(..) => {}
+            MaybeString::Owned(ref mut s) => s.push(ch),
+        }
+    }
+
+    fn to_owned(&mut self, input: &str) {
+        match *self {
+            MaybeString::NotEscaped(start) => {
+                *self = MaybeString::Owned(input[start..].to_owned());
+            }
+            MaybeString::Owned(..) => {}
+        }
+    }
+
+    fn into_cow(self, input: &str) -> Cow<'_, str> {
+        match self {
+            MaybeString::NotEscaped(start) => Cow::Borrowed(&input[start..]),
+            MaybeString::Owned(s) => Cow::Owned(s),
+        }
+    }
+}
+
+fn is_keylike(ch: char) -> bool {
+    ('A' <= ch && ch <= 'Z')
+        || ('a' <= ch && ch <= 'z')
+        || ('0' <= ch && ch <= '9')
+        || ch == '-'
+        || ch == '_'
+}
+
+impl<'a> Token<'a> {
+    pub fn describe(&self) -> &'static str {
+        match *self {
+            Token::Keylike(_) => "an identifier",
+            Token::Equals => "an equals",
+            Token::Period => "a period",
+            Token::Comment(_) => "a comment",
+            Token::Newline => "a newline",
+            Token::Whitespace(_) => "whitespace",
+            Token::Comma => "a comma",
+            Token::RightBrace => "a right brace",
+            Token::LeftBrace => "a left brace",
+            Token::RightBracket => "a right bracket",
+            Token::LeftBracket => "a left bracket",
+            Token::String { multiline, .. } => {
+                if multiline {
+                    "a multiline string"
+                } else {
+                    "a string"
+                }
+            }
+            Token::Colon => "a colon",
+            Token::Plus => "a plus",
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{Error, Token, Tokenizer};
+    use std::borrow::Cow;
+
+    fn err(input: &str, err: Error) {
+        let mut t = Tokenizer::new(input);
+        let token = t.next().unwrap_err();
+        assert_eq!(token, err);
+        assert!(t.next().unwrap().is_none());
+    }
+
+    #[test]
+    fn literal_strings() {
+        fn t(input: &str, val: &str, multiline: bool) {
+            let mut t = Tokenizer::new(input);
+            let (_, token) = t.next().unwrap().unwrap();
+            assert_eq!(
+                token,
+                Token::String {
+                    src: input,
+                    val: Cow::Borrowed(val),
+                    multiline: multiline,
+                }
+            );
+            assert!(t.next().unwrap().is_none());
+        }
+
+        t("''", "", false);
+        t("''''''", "", true);
+        t("'''\n'''", "", true);
+        t("'a'", "a", false);
+        t("'\"a'", "\"a", false);
+        t("''''a'''", "'a", true);
+        t("'''\n'a\n'''", "'a\n", true);
+        t("'''a\n'a\r\n'''", "a\n'a\n", true);
+    }
+
+    #[test]
+    fn basic_strings() {
+        fn t(input: &str, val: &str, multiline: bool) {
+            let mut t = Tokenizer::new(input);
+            let (_, token) = t.next().unwrap().unwrap();
+            assert_eq!(
+                token,
+                Token::String {
+                    src: input,
+                    val: Cow::Borrowed(val),
+                    multiline: multiline,
+                }
+            );
+            assert!(t.next().unwrap().is_none());
+        }
+
+        t(r#""""#, "", false);
+        t(r#""""""""#, "", true);
+        t(r#""a""#, "a", false);
+        t(r#""""a""""#, "a", true);
+        t(r#""\t""#, "\t", false);
+        t(r#""\u0000""#, "\0", false);
+        t(r#""\U00000000""#, "\0", false);
+        t(r#""\U000A0000""#, "\u{A0000}", false);
+        t(r#""\\t""#, "\\t", false);
+        t("\"\t\"", "\t", false);
+        t("\"\"\"\n\t\"\"\"", "\t", true);
+        t("\"\"\"\\\n\"\"\"", "", true);
+        t(
+            "\"\"\"\\\n     \t   \t  \\\r\n  \t \n  \t \r\n\"\"\"",
+            "",
+            true,
+        );
+        t(r#""\r""#, "\r", false);
+        t(r#""\n""#, "\n", false);
+        t(r#""\b""#, "\u{8}", false);
+        t(r#""a\fa""#, "a\u{c}a", false);
+        t(r#""\"a""#, "\"a", false);
+        t("\"\"\"\na\"\"\"", "a", true);
+        t("\"\"\"\n\"\"\"", "", true);
+        t(r#""""a\"""b""""#, "a\"\"\"b", true);
+        err(r#""\a"#, Error::InvalidEscape(2, 'a'));
+        err("\"\\\n", Error::InvalidEscape(2, '\n'));
+        err("\"\\\r\n", Error::InvalidEscape(2, '\n'));
+        err("\"\\", Error::UnterminatedString(0));
+        err("\"\u{0}", Error::InvalidCharInString(1, '\u{0}'));
+        err(r#""\U00""#, Error::InvalidHexEscape(5, '"'));
+        err(r#""\U00"#, Error::UnterminatedString(0));
+        err(r#""\uD800"#, Error::InvalidEscapeValue(2, 0xd800));
+        err(r#""\UFFFFFFFF"#, Error::InvalidEscapeValue(2, 0xffff_ffff));
+    }
+
+    #[test]
+    fn keylike() {
+        fn t(input: &str) {
+            let mut t = Tokenizer::new(input);
+            let (_, token) = t.next().unwrap().unwrap();
+            assert_eq!(token, Token::Keylike(input));
+            assert!(t.next().unwrap().is_none());
+        }
+        t("foo");
+        t("0bar");
+        t("bar0");
+        t("1234");
+        t("a-b");
+        t("a_B");
+        t("-_-");
+        t("___");
+    }
+
+    #[test]
+    fn all() {
+        fn t(input: &str, expected: &[((usize, usize), Token<'_>, &str)]) {
+            let mut tokens = Tokenizer::new(input);
+            let mut actual: Vec<((usize, usize), Token<'_>, &str)> = Vec::new();
+            while let Some((span, token)) = tokens.next().unwrap() {
+                actual.push((span.into(), token, &input[span.start..span.end]));
+            }
+            for (a, b) in actual.iter().zip(expected) {
+                assert_eq!(a, b);
+            }
+            assert_eq!(actual.len(), expected.len());
+        }
+
+        t(
+            " a ",
+            &[
+                ((0, 1), Token::Whitespace(" "), " "),
+                ((1, 2), Token::Keylike("a"), "a"),
+                ((2, 3), Token::Whitespace(" "), " "),
+            ],
+        );
+
+        t(
+            " a\t [[]] \t [] {} , . =\n# foo \r\n#foo \n ",
+            &[
+                ((0, 1), Token::Whitespace(" "), " "),
+                ((1, 2), Token::Keylike("a"), "a"),
+                ((2, 4), Token::Whitespace("\t "), "\t "),
+                ((4, 5), Token::LeftBracket, "["),
+                ((5, 6), Token::LeftBracket, "["),
+                ((6, 7), Token::RightBracket, "]"),
+                ((7, 8), Token::RightBracket, "]"),
+                ((8, 11), Token::Whitespace(" \t "), " \t "),
+                ((11, 12), Token::LeftBracket, "["),
+                ((12, 13), Token::RightBracket, "]"),
+                ((13, 14), Token::Whitespace(" "), " "),
+                ((14, 15), Token::LeftBrace, "{"),
+                ((15, 16), Token::RightBrace, "}"),
+                ((16, 17), Token::Whitespace(" "), " "),
+                ((17, 18), Token::Comma, ","),
+                ((18, 19), Token::Whitespace(" "), " "),
+                ((19, 20), Token::Period, "."),
+                ((20, 21), Token::Whitespace(" "), " "),
+                ((21, 22), Token::Equals, "="),
+                ((22, 23), Token::Newline, "\n"),
+                ((23, 29), Token::Comment("# foo "), "# foo "),
+                ((29, 31), Token::Newline, "\r\n"),
+                ((31, 36), Token::Comment("#foo "), "#foo "),
+                ((36, 37), Token::Newline, "\n"),
+                ((37, 38), Token::Whitespace(" "), " "),
+            ],
+        );
+    }
+
+    #[test]
+    fn bare_cr_bad() {
+        err("\r", Error::Unexpected(0, '\r'));
+        err("'\n", Error::NewlineInString(1));
+        err("'\u{0}", Error::InvalidCharInString(1, '\u{0}'));
+        err("'", Error::UnterminatedString(0));
+        err("\u{0}", Error::Unexpected(0, '\u{0}'));
+    }
+
+    #[test]
+    fn bad_comment() {
+        let mut t = Tokenizer::new("#\u{0}");
+        t.next().unwrap().unwrap();
+        assert_eq!(t.next(), Err(Error::Unexpected(1, '\u{0}')));
+        assert!(t.next().unwrap().is_none());
+    }
+}
diff --git a/crates/toml/src/value.rs b/crates/toml/src/value.rs
new file mode 100644
index 00000000..1a1756d1
--- /dev/null
+++ b/crates/toml/src/value.rs
@@ -0,0 +1,1081 @@
+//! Definition of a TOML value
+
+use std::collections::{BTreeMap, HashMap};
+use std::fmt;
+use std::hash::Hash;
+use std::mem::discriminant;
+use std::ops;
+use std::str::FromStr;
+use std::vec;
+
+use serde::de;
+use serde::de::IntoDeserializer;
+use serde::ser;
+
+use crate::datetime::{self, DatetimeFromString};
+pub use crate::datetime::{Date, Datetime, DatetimeParseError, Offset, Time};
+
+pub use crate::map::{Entry, Map};
+
+/// Representation of a TOML value.
+#[derive(PartialEq, Clone, Debug)]
+pub enum Value {
+    /// Represents a TOML string
+    String(String),
+    /// Represents a TOML integer
+    Integer(i64),
+    /// Represents a TOML float
+    Float(f64),
+    /// Represents a TOML boolean
+    Boolean(bool),
+    /// Represents a TOML datetime
+    Datetime(Datetime),
+    /// Represents a TOML array
+    Array(Array),
+    /// Represents a TOML table
+    Table(Table),
+}
+
+/// Type representing a TOML array, payload of the `Value::Array` variant
+pub type Array = Vec<Value>;
+
+/// Type representing a TOML table, payload of the `Value::Table` variant.
+/// By default it is backed by a BTreeMap, enable the `preserve_order` feature
+/// to use a LinkedHashMap instead.
+pub type Table = Map<String, Value>;
+
+impl Value {
+    /// Convert a `T` into `toml::Value` which is an enum that can represent
+    /// any valid TOML data.
+    ///
+    /// This conversion can fail if `T`'s implementation of `Serialize` decides to
+    /// fail, or if `T` contains a map with non-string keys.
+    pub fn try_from<T>(value: T) -> Result<Value, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(Serializer)
+    }
+
+    /// Interpret a `toml::Value` as an instance of type `T`.
+    ///
+    /// This conversion can fail if the structure of the `Value` does not match the
+    /// structure expected by `T`, for example if `T` is a struct type but the
+    /// `Value` contains something other than a TOML table. It can also fail if the
+    /// structure is correct but `T`'s implementation of `Deserialize` decides that
+    /// something is wrong with the data, for example required struct fields are
+    /// missing from the TOML map or some number is too big to fit in the expected
+    /// primitive type.
+    pub fn try_into<'de, T>(self) -> Result<T, crate::de::Error>
+    where
+        T: de::Deserialize<'de>,
+    {
+        de::Deserialize::deserialize(self)
+    }
+
+    /// Index into a TOML array or map. A string index can be used to access a
+    /// value in a map, and a usize index can be used to access an element of an
+    /// array.
+    ///
+    /// Returns `None` if the type of `self` does not match the type of the
+    /// index, for example if the index is a string and `self` is an array or a
+    /// number. Also returns `None` if the given key does not exist in the map
+    /// or the given index is not within the bounds of the array.
+    pub fn get<I: Index>(&self, index: I) -> Option<&Value> {
+        index.index(self)
+    }
+
+    /// Mutably index into a TOML array or map. A string index can be used to
+    /// access a value in a map, and a usize index can be used to access an
+    /// element of an array.
+    ///
+    /// Returns `None` if the type of `self` does not match the type of the
+    /// index, for example if the index is a string and `self` is an array or a
+    /// number. Also returns `None` if the given key does not exist in the map
+    /// or the given index is not within the bounds of the array.
+    pub fn get_mut<I: Index>(&mut self, index: I) -> Option<&mut Value> {
+        index.index_mut(self)
+    }
+
+    /// Extracts the integer value if it is an integer.
+    pub fn as_integer(&self) -> Option<i64> {
+        match *self {
+            Value::Integer(i) => Some(i),
+            _ => None,
+        }
+    }
+
+    /// Tests whether this value is an integer.
+    pub fn is_integer(&self) -> bool {
+        self.as_integer().is_some()
+    }
+
+    /// Extracts the float value if it is a float.
+    pub fn as_float(&self) -> Option<f64> {
+        match *self {
+            Value::Float(f) => Some(f),
+            _ => None,
+        }
+    }
+
+    /// Tests whether this value is a float.
+    pub fn is_float(&self) -> bool {
+        self.as_float().is_some()
+    }
+
+    /// Extracts the boolean value if it is a boolean.
+    pub fn as_bool(&self) -> Option<bool> {
+        match *self {
+            Value::Boolean(b) => Some(b),
+            _ => None,
+        }
+    }
+
+    /// Tests whether this value is a boolean.
+    pub fn is_bool(&self) -> bool {
+        self.as_bool().is_some()
+    }
+
+    /// Extracts the string of this value if it is a string.
+    pub fn as_str(&self) -> Option<&str> {
+        match *self {
+            Value::String(ref s) => Some(&**s),
+            _ => None,
+        }
+    }
+
+    /// Tests if this value is a string.
+    pub fn is_str(&self) -> bool {
+        self.as_str().is_some()
+    }
+
+    /// Extracts the datetime value if it is a datetime.
+    ///
+    /// Note that a parsed TOML value will only contain ISO 8601 dates. An
+    /// example date is:
+    ///
+    /// ```notrust
+    /// 1979-05-27T07:32:00Z
+    /// ```
+    pub fn as_datetime(&self) -> Option<&Datetime> {
+        match *self {
+            Value::Datetime(ref s) => Some(s),
+            _ => None,
+        }
+    }
+
+    /// Tests whether this value is a datetime.
+    pub fn is_datetime(&self) -> bool {
+        self.as_datetime().is_some()
+    }
+
+    /// Extracts the array value if it is an array.
+    pub fn as_array(&self) -> Option<&Vec<Value>> {
+        match *self {
+            Value::Array(ref s) => Some(s),
+            _ => None,
+        }
+    }
+
+    /// Extracts the array value if it is an array.
+    pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value>> {
+        match *self {
+            Value::Array(ref mut s) => Some(s),
+            _ => None,
+        }
+    }
+
+    /// Tests whether this value is an array.
+    pub fn is_array(&self) -> bool {
+        self.as_array().is_some()
+    }
+
+    /// Extracts the table value if it is a table.
+    pub fn as_table(&self) -> Option<&Table> {
+        match *self {
+            Value::Table(ref s) => Some(s),
+            _ => None,
+        }
+    }
+
+    /// Extracts the table value if it is a table.
+    pub fn as_table_mut(&mut self) -> Option<&mut Table> {
+        match *self {
+            Value::Table(ref mut s) => Some(s),
+            _ => None,
+        }
+    }
+
+    /// Tests whether this value is a table.
+    pub fn is_table(&self) -> bool {
+        self.as_table().is_some()
+    }
+
+    /// Tests whether this and another value have the same type.
+    pub fn same_type(&self, other: &Value) -> bool {
+        discriminant(self) == discriminant(other)
+    }
+
+    /// Returns a human-readable representation of the type of this value.
+    pub fn type_str(&self) -> &'static str {
+        match *self {
+            Value::String(..) => "string",
+            Value::Integer(..) => "integer",
+            Value::Float(..) => "float",
+            Value::Boolean(..) => "boolean",
+            Value::Datetime(..) => "datetime",
+            Value::Array(..) => "array",
+            Value::Table(..) => "table",
+        }
+    }
+}
+
+impl<I> ops::Index<I> for Value
+where
+    I: Index,
+{
+    type Output = Value;
+
+    fn index(&self, index: I) -> &Value {
+        self.get(index).expect("index not found")
+    }
+}
+
+impl<I> ops::IndexMut<I> for Value
+where
+    I: Index,
+{
+    fn index_mut(&mut self, index: I) -> &mut Value {
+        self.get_mut(index).expect("index not found")
+    }
+}
+
+impl<'a> From<&'a str> for Value {
+    #[inline]
+    fn from(val: &'a str) -> Value {
+        Value::String(val.to_string())
+    }
+}
+
+impl<V: Into<Value>> From<Vec<V>> for Value {
+    fn from(val: Vec<V>) -> Value {
+        Value::Array(val.into_iter().map(|v| v.into()).collect())
+    }
+}
+
+impl<S: Into<String>, V: Into<Value>> From<BTreeMap<S, V>> for Value {
+    fn from(val: BTreeMap<S, V>) -> Value {
+        let table = val.into_iter().map(|(s, v)| (s.into(), v.into())).collect();
+
+        Value::Table(table)
+    }
+}
+
+impl<S: Into<String> + Hash + Eq, V: Into<Value>> From<HashMap<S, V>> for Value {
+    fn from(val: HashMap<S, V>) -> Value {
+        let table = val.into_iter().map(|(s, v)| (s.into(), v.into())).collect();
+
+        Value::Table(table)
+    }
+}
+
+macro_rules! impl_into_value {
+    ($variant:ident : $T:ty) => {
+        impl From<$T> for Value {
+            #[inline]
+            fn from(val: $T) -> Value {
+                Value::$variant(val.into())
+            }
+        }
+    };
+}
+
+impl_into_value!(String: String);
+impl_into_value!(Integer: i64);
+impl_into_value!(Integer: i32);
+impl_into_value!(Integer: i8);
+impl_into_value!(Integer: u8);
+impl_into_value!(Integer: u32);
+impl_into_value!(Float: f64);
+impl_into_value!(Float: f32);
+impl_into_value!(Boolean: bool);
+impl_into_value!(Datetime: Datetime);
+impl_into_value!(Table: Table);
+
+/// Types that can be used to index a `toml::Value`
+///
+/// Currently this is implemented for `usize` to index arrays and `str` to index
+/// tables.
+///
+/// This trait is sealed and not intended for implementation outside of the
+/// `toml` crate.
+pub trait Index: Sealed {
+    #[doc(hidden)]
+    fn index<'a>(&self, val: &'a Value) -> Option<&'a Value>;
+    #[doc(hidden)]
+    fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value>;
+}
+
+/// An implementation detail that should not be implemented, this will change in
+/// the future and break code otherwise.
+#[doc(hidden)]
+pub trait Sealed {}
+impl Sealed for usize {}
+impl Sealed for str {}
+impl Sealed for String {}
+impl<'a, T: Sealed + ?Sized> Sealed for &'a T {}
+
+impl Index for usize {
+    fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> {
+        match *val {
+            Value::Array(ref a) => a.get(*self),
+            _ => None,
+        }
+    }
+
+    fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> {
+        match *val {
+            Value::Array(ref mut a) => a.get_mut(*self),
+            _ => None,
+        }
+    }
+}
+
+impl Index for str {
+    fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> {
+        match *val {
+            Value::Table(ref a) => a.get(self),
+            _ => None,
+        }
+    }
+
+    fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> {
+        match *val {
+            Value::Table(ref mut a) => a.get_mut(self),
+            _ => None,
+        }
+    }
+}
+
+impl Index for String {
+    fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> {
+        self[..].index(val)
+    }
+
+    fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> {
+        self[..].index_mut(val)
+    }
+}
+
+impl<'s, T: ?Sized> Index for &'s T
+where
+    T: Index,
+{
+    fn index<'a>(&self, val: &'a Value) -> Option<&'a Value> {
+        (**self).index(val)
+    }
+
+    fn index_mut<'a>(&self, val: &'a mut Value) -> Option<&'a mut Value> {
+        (**self).index_mut(val)
+    }
+}
+
+impl fmt::Display for Value {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        crate::ser::to_string(self)
+            .expect("Unable to represent value as string")
+            .fmt(f)
+    }
+}
+
+impl FromStr for Value {
+    type Err = crate::de::Error;
+    fn from_str(s: &str) -> Result<Value, Self::Err> {
+        crate::from_str(s)
+    }
+}
+
+impl ser::Serialize for Value {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: ser::Serializer,
+    {
+        use serde::ser::SerializeMap;
+
+        match *self {
+            Value::String(ref s) => serializer.serialize_str(s),
+            Value::Integer(i) => serializer.serialize_i64(i),
+            Value::Float(f) => serializer.serialize_f64(f),
+            Value::Boolean(b) => serializer.serialize_bool(b),
+            Value::Datetime(ref s) => s.serialize(serializer),
+            Value::Array(ref a) => a.serialize(serializer),
+            Value::Table(ref t) => {
+                let mut map = serializer.serialize_map(Some(t.len()))?;
+                // Be sure to visit non-tables first (and also non
+                // array-of-tables) as all keys must be emitted first.
+                for (k, v) in t {
+                    if !v.is_table() && !v.is_array()
+                        || (v
+                            .as_array()
+                            .map(|a| !a.iter().any(|v| v.is_table()))
+                            .unwrap_or(false))
+                    {
+                        map.serialize_entry(k, v)?;
+                    }
+                }
+                for (k, v) in t {
+                    if v.as_array()
+                        .map(|a| a.iter().any(|v| v.is_table()))
+                        .unwrap_or(false)
+                    {
+                        map.serialize_entry(k, v)?;
+                    }
+                }
+                for (k, v) in t {
+                    if v.is_table() {
+                        map.serialize_entry(k, v)?;
+                    }
+                }
+                map.end()
+            }
+        }
+    }
+}
+
+impl<'de> de::Deserialize<'de> for Value {
+    fn deserialize<D>(deserializer: D) -> Result<Value, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        struct ValueVisitor;
+
+        impl<'de> de::Visitor<'de> for ValueVisitor {
+            type Value = Value;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+                formatter.write_str("any valid TOML value")
+            }
+
+            fn visit_bool<E>(self, value: bool) -> Result<Value, E> {
+                Ok(Value::Boolean(value))
+            }
+
+            fn visit_i64<E>(self, value: i64) -> Result<Value, E> {
+                Ok(Value::Integer(value))
+            }
+
+            fn visit_u64<E: de::Error>(self, value: u64) -> Result<Value, E> {
+                if value <= i64::max_value() as u64 {
+                    Ok(Value::Integer(value as i64))
+                } else {
+                    Err(de::Error::custom("u64 value was too large"))
+                }
+            }
+
+            fn visit_u32<E>(self, value: u32) -> Result<Value, E> {
+                Ok(Value::Integer(value.into()))
+            }
+
+            fn visit_i32<E>(self, value: i32) -> Result<Value, E> {
+                Ok(Value::Integer(value.into()))
+            }
+
+            fn visit_f64<E>(self, value: f64) -> Result<Value, E> {
+                Ok(Value::Float(value))
+            }
+
+            fn visit_str<E>(self, value: &str) -> Result<Value, E> {
+                Ok(Value::String(value.into()))
+            }
+
+            fn visit_string<E>(self, value: String) -> Result<Value, E> {
+                Ok(Value::String(value))
+            }
+
+            fn visit_some<D>(self, deserializer: D) -> Result<Value, D::Error>
+            where
+                D: de::Deserializer<'de>,
+            {
+                de::Deserialize::deserialize(deserializer)
+            }
+
+            fn visit_seq<V>(self, mut visitor: V) -> Result<Value, V::Error>
+            where
+                V: de::SeqAccess<'de>,
+            {
+                let mut vec = Vec::new();
+                while let Some(elem) = visitor.next_element()? {
+                    vec.push(elem);
+                }
+                Ok(Value::Array(vec))
+            }
+
+            fn visit_map<V>(self, mut visitor: V) -> Result<Value, V::Error>
+            where
+                V: de::MapAccess<'de>,
+            {
+                let mut key = String::new();
+                let datetime = visitor.next_key_seed(DatetimeOrTable { key: &mut key })?;
+                match datetime {
+                    Some(true) => {
+                        let date: DatetimeFromString = visitor.next_value()?;
+                        return Ok(Value::Datetime(date.value));
+                    }
+                    None => return Ok(Value::Table(Map::new())),
+                    Some(false) => {}
+                }
+                let mut map = Map::new();
+                map.insert(key, visitor.next_value()?);
+                while let Some(key) = visitor.next_key::<String>()? {
+                    if let Entry::Vacant(vacant) = map.entry(&key) {
+                        vacant.insert(visitor.next_value()?);
+                    } else {
+                        let msg = format!("duplicate key: `{}`", key);
+                        return Err(de::Error::custom(msg));
+                    }
+                }
+                Ok(Value::Table(map))
+            }
+        }
+
+        deserializer.deserialize_any(ValueVisitor)
+    }
+}
+
+impl<'de> de::Deserializer<'de> for Value {
+    type Error = crate::de::Error;
+
+    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, crate::de::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self {
+            Value::Boolean(v) => visitor.visit_bool(v),
+            Value::Integer(n) => visitor.visit_i64(n),
+            Value::Float(n) => visitor.visit_f64(n),
+            Value::String(v) => visitor.visit_string(v),
+            Value::Datetime(v) => visitor.visit_string(v.to_string()),
+            Value::Array(v) => {
+                let len = v.len();
+                let mut deserializer = SeqDeserializer::new(v);
+                let seq = visitor.visit_seq(&mut deserializer)?;
+                let remaining = deserializer.iter.len();
+                if remaining == 0 {
+                    Ok(seq)
+                } else {
+                    Err(de::Error::invalid_length(len, &"fewer elements in array"))
+                }
+            }
+            Value::Table(v) => {
+                let len = v.len();
+                let mut deserializer = MapDeserializer::new(v);
+                let map = visitor.visit_map(&mut deserializer)?;
+                let remaining = deserializer.iter.len();
+                if remaining == 0 {
+                    Ok(map)
+                } else {
+                    Err(de::Error::invalid_length(len, &"fewer elements in map"))
+                }
+            }
+        }
+    }
+
+    #[inline]
+    fn deserialize_enum<V>(
+        self,
+        _name: &str,
+        _variants: &'static [&'static str],
+        visitor: V,
+    ) -> Result<V::Value, crate::de::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        match self {
+            Value::String(variant) => visitor.visit_enum(variant.into_deserializer()),
+            _ => Err(de::Error::invalid_type(
+                de::Unexpected::UnitVariant,
+                &"string only",
+            )),
+        }
+    }
+
+    // `None` is interpreted as a missing field so be sure to implement `Some`
+    // as a present field.
+    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, crate::de::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_some(self)
+    }
+
+    fn deserialize_newtype_struct<V>(
+        self,
+        _name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, crate::de::Error>
+    where
+        V: de::Visitor<'de>,
+    {
+        visitor.visit_newtype_struct(self)
+    }
+
+    serde::forward_to_deserialize_any! {
+        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq
+        bytes byte_buf map unit_struct tuple_struct struct
+        tuple ignored_any identifier
+    }
+}
+
+struct SeqDeserializer {
+    iter: vec::IntoIter<Value>,
+}
+
+impl SeqDeserializer {
+    fn new(vec: Vec<Value>) -> Self {
+        SeqDeserializer {
+            iter: vec.into_iter(),
+        }
+    }
+}
+
+impl<'de> de::SeqAccess<'de> for SeqDeserializer {
+    type Error = crate::de::Error;
+
+    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, crate::de::Error>
+    where
+        T: de::DeserializeSeed<'de>,
+    {
+        match self.iter.next() {
+            Some(value) => seed.deserialize(value).map(Some),
+            None => Ok(None),
+        }
+    }
+
+    fn size_hint(&self) -> Option<usize> {
+        match self.iter.size_hint() {
+            (lower, Some(upper)) if lower == upper => Some(upper),
+            _ => None,
+        }
+    }
+}
+
+struct MapDeserializer {
+    iter: <Map<String, Value> as IntoIterator>::IntoIter,
+    value: Option<(String, Value)>,
+}
+
+impl MapDeserializer {
+    fn new(map: Map<String, Value>) -> Self {
+        MapDeserializer {
+            iter: map.into_iter(),
+            value: None,
+        }
+    }
+}
+
+impl<'de> de::MapAccess<'de> for MapDeserializer {
+    type Error = crate::de::Error;
+
+    fn next_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, crate::de::Error>
+    where
+        T: de::DeserializeSeed<'de>,
+    {
+        match self.iter.next() {
+            Some((key, value)) => {
+                self.value = Some((key.clone(), value));
+                seed.deserialize(Value::String(key)).map(Some)
+            }
+            None => Ok(None),
+        }
+    }
+
+    fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value, crate::de::Error>
+    where
+        T: de::DeserializeSeed<'de>,
+    {
+        let (key, res) = match self.value.take() {
+            Some((key, value)) => (key, seed.deserialize(value)),
+            None => return Err(de::Error::custom("value is missing")),
+        };
+        res.map_err(|mut error| {
+            error.add_key_context(&key);
+            error
+        })
+    }
+
+    fn size_hint(&self) -> Option<usize> {
+        match self.iter.size_hint() {
+            (lower, Some(upper)) if lower == upper => Some(upper),
+            _ => None,
+        }
+    }
+}
+
+impl<'de> de::IntoDeserializer<'de, crate::de::Error> for Value {
+    type Deserializer = Self;
+
+    fn into_deserializer(self) -> Self {
+        self
+    }
+}
+
+struct Serializer;
+
+impl ser::Serializer for Serializer {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    type SerializeSeq = SerializeVec;
+    type SerializeTuple = SerializeVec;
+    type SerializeTupleStruct = SerializeVec;
+    type SerializeTupleVariant = SerializeVec;
+    type SerializeMap = SerializeMap;
+    type SerializeStruct = SerializeMap;
+    type SerializeStructVariant = ser::Impossible<Value, crate::ser::Error>;
+
+    fn serialize_bool(self, value: bool) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Boolean(value))
+    }
+
+    fn serialize_i8(self, value: i8) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_i16(self, value: i16) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_i32(self, value: i32) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_i64(self, value: i64) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Integer(value))
+    }
+
+    fn serialize_u8(self, value: u8) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_u16(self, value: u16) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_u32(self, value: u32) -> Result<Value, crate::ser::Error> {
+        self.serialize_i64(value.into())
+    }
+
+    fn serialize_u64(self, value: u64) -> Result<Value, crate::ser::Error> {
+        if value <= i64::max_value() as u64 {
+            self.serialize_i64(value as i64)
+        } else {
+            Err(ser::Error::custom("u64 value was too large"))
+        }
+    }
+
+    fn serialize_f32(self, value: f32) -> Result<Value, crate::ser::Error> {
+        self.serialize_f64(value.into())
+    }
+
+    fn serialize_f64(self, value: f64) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Float(value))
+    }
+
+    fn serialize_char(self, value: char) -> Result<Value, crate::ser::Error> {
+        let mut s = String::new();
+        s.push(value);
+        self.serialize_str(&s)
+    }
+
+    fn serialize_str(self, value: &str) -> Result<Value, crate::ser::Error> {
+        Ok(Value::String(value.to_owned()))
+    }
+
+    fn serialize_bytes(self, value: &[u8]) -> Result<Value, crate::ser::Error> {
+        let vec = value.iter().map(|&b| Value::Integer(b.into())).collect();
+        Ok(Value::Array(vec))
+    }
+
+    fn serialize_unit(self) -> Result<Value, crate::ser::Error> {
+        Err(crate::ser::Error::UnsupportedType)
+    }
+
+    fn serialize_unit_struct(self, _name: &'static str) -> Result<Value, crate::ser::Error> {
+        Err(crate::ser::Error::UnsupportedType)
+    }
+
+    fn serialize_unit_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+    ) -> Result<Value, crate::ser::Error> {
+        self.serialize_str(_variant)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized>(
+        self,
+        _name: &'static str,
+        value: &T,
+    ) -> Result<Value, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized>(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _value: &T,
+    ) -> Result<Value, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        Err(crate::ser::Error::UnsupportedType)
+    }
+
+    fn serialize_none(self) -> Result<Value, crate::ser::Error> {
+        Err(crate::ser::Error::UnsupportedNone)
+    }
+
+    fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Value, crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        value.serialize(self)
+    }
+
+    fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, crate::ser::Error> {
+        Ok(SerializeVec {
+            vec: Vec::with_capacity(len.unwrap_or(0)),
+        })
+    }
+
+    fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, crate::ser::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleStruct, crate::ser::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeTupleVariant, crate::ser::Error> {
+        self.serialize_seq(Some(len))
+    }
+
+    fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, crate::ser::Error> {
+        Ok(SerializeMap {
+            map: Map::new(),
+            next_key: None,
+        })
+    }
+
+    fn serialize_struct(
+        self,
+        _name: &'static str,
+        len: usize,
+    ) -> Result<Self::SerializeStruct, crate::ser::Error> {
+        self.serialize_map(Some(len))
+    }
+
+    fn serialize_struct_variant(
+        self,
+        _name: &'static str,
+        _variant_index: u32,
+        _variant: &'static str,
+        _len: usize,
+    ) -> Result<Self::SerializeStructVariant, crate::ser::Error> {
+        Err(crate::ser::Error::UnsupportedType)
+    }
+}
+
+struct SerializeVec {
+    vec: Vec<Value>,
+}
+
+struct SerializeMap {
+    map: Map<String, Value>,
+    next_key: Option<String>,
+}
+
+impl ser::SerializeSeq for SerializeVec {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        self.vec.push(Value::try_from(value)?);
+        Ok(())
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Array(self.vec))
+    }
+}
+
+impl ser::SerializeTuple for SerializeVec {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+impl ser::SerializeTupleStruct for SerializeVec {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+impl ser::SerializeTupleVariant for SerializeVec {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeSeq::serialize_element(self, value)
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        ser::SerializeSeq::end(self)
+    }
+}
+
+impl ser::SerializeMap for SerializeMap {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        match Value::try_from(key)? {
+            Value::String(s) => self.next_key = Some(s),
+            _ => return Err(crate::ser::Error::KeyNotString),
+        };
+        Ok(())
+    }
+
+    fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        let key = self.next_key.take();
+        let key = key.expect("serialize_value called before serialize_key");
+        match Value::try_from(value) {
+            Ok(value) => {
+                self.map.insert(key, value);
+            }
+            Err(crate::ser::Error::UnsupportedNone) => {}
+            Err(e) => return Err(e),
+        }
+        Ok(())
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        Ok(Value::Table(self.map))
+    }
+}
+
+impl ser::SerializeStruct for SerializeMap {
+    type Ok = Value;
+    type Error = crate::ser::Error;
+
+    fn serialize_field<T: ?Sized>(
+        &mut self,
+        key: &'static str,
+        value: &T,
+    ) -> Result<(), crate::ser::Error>
+    where
+        T: ser::Serialize,
+    {
+        ser::SerializeMap::serialize_key(self, key)?;
+        ser::SerializeMap::serialize_value(self, value)
+    }
+
+    fn end(self) -> Result<Value, crate::ser::Error> {
+        ser::SerializeMap::end(self)
+    }
+}
+
+struct DatetimeOrTable<'a> {
+    key: &'a mut String,
+}
+
+impl<'a, 'de> de::DeserializeSeed<'de> for DatetimeOrTable<'a> {
+    type Value = bool;
+
+    fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        deserializer.deserialize_any(self)
+    }
+}
+
+impl<'a, 'de> de::Visitor<'de> for DatetimeOrTable<'a> {
+    type Value = bool;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        formatter.write_str("a string key")
+    }
+
+    fn visit_str<E>(self, s: &str) -> Result<bool, E>
+    where
+        E: de::Error,
+    {
+        if s == datetime::FIELD {
+            Ok(true)
+        } else {
+            self.key.push_str(s);
+            Ok(false)
+        }
+    }
+
+    fn visit_string<E>(self, s: String) -> Result<bool, E>
+    where
+        E: de::Error,
+    {
+        if s == datetime::FIELD {
+            Ok(true)
+        } else {
+            *self.key = s;
+            Ok(false)
+        }
+    }
+}
diff --git a/CHANGELOG.md b/crates/toml_edit/CHANGELOG.md
similarity index 100%
rename from CHANGELOG.md
rename to crates/toml_edit/CHANGELOG.md
diff --git a/crates/toml_edit/Cargo.toml b/crates/toml_edit/Cargo.toml
new file mode 100644
index 00000000..e94ff498
--- /dev/null
+++ b/crates/toml_edit/Cargo.toml
@@ -0,0 +1,85 @@
+[package]
+name = "toml_edit"
+version = "0.14.4"
+readme = "README.md"
+license = "MIT/Apache-2.0"
+keywords = ["toml"]
+categories = ["encoding", "parser-implementations", "parsing"]
+description = "Yet another format-preserving TOML parser."
+authors = ["Andronik Ordian <write@reusable.software>", "Ed Page <eopage@gmail.com>"]
+repository = "https://github.com/ordian/toml_edit"
+documentation = "https://docs.rs/toml_edit"
+edition = "2018"
+include = [
+  "src/**/*",
+  "Cargo.toml",
+  "LICENSE*",
+  "README.md",
+  "examples/**/*",
+  "benches/**/*",
+  "tests/**/*"
+]
+
+[package.metadata.docs.rs]
+features = ["easy"]
+
+[package.metadata.release]
+pre-release-replacements = [
+  {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1},
+  {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1},
+  {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1},
+  {file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## [Unreleased] - ReleaseDate\n", exactly=1},
+  {file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/ordian/toml_edit/compare/{{tag_name}}...HEAD", exactly=1},
+]
+
+[features]
+default = []
+easy = ["serde"]
+perf = ["kstring"]
+
+[dependencies]
+indexmap = "1.7"
+combine = "4.6.3"
+itertools = "0.10"
+serde = { version = "1", features = ["derive"], optional = true }
+kstring = { version = "2.0.0", features = ["max_inline"], optional = true }
+
+[dev-dependencies]
+serde_json = "1.0.44"
+pretty_assertions = "1.0.0"
+toml-test-harness = "0.3"
+snapbox = { version = "0.3.3", features = ["harness"] }
+criterion = "0.3"
+toml = "0.5"
+
+[[test]]
+name = "decoder_compliance"
+harness = false
+
+[[test]]
+name = "encoder_compliance"
+harness = false
+
+[[test]]
+name = "easy_decoder_compliance"
+harness = false
+
+[[test]]
+name = "easy_encoder_compliance"
+harness = false
+
+[[test]]
+name = "test_invalid"
+harness = false
+
+[[bench]]
+name = "cargo"
+harness = false
+
+[[bench]]
+name = "linear"
+harness = false
+
+[[example]]
+name = "visit"
+test = true
diff --git a/crates/toml_edit/LICENSE-APACHE b/crates/toml_edit/LICENSE-APACHE
new file mode 100644
index 00000000..16fe87b0
--- /dev/null
+++ b/crates/toml_edit/LICENSE-APACHE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSE-MIT b/crates/toml_edit/LICENSE-MIT
similarity index 100%
rename from LICENSE-MIT
rename to crates/toml_edit/LICENSE-MIT
diff --git a/README.md b/crates/toml_edit/README.md
similarity index 100%
rename from README.md
rename to crates/toml_edit/README.md
diff --git a/benches/cargo.rs b/crates/toml_edit/benches/cargo.rs
similarity index 100%
rename from benches/cargo.rs
rename to crates/toml_edit/benches/cargo.rs
diff --git a/benches/linear.rs b/crates/toml_edit/benches/linear.rs
similarity index 100%
rename from benches/linear.rs
rename to crates/toml_edit/benches/linear.rs
diff --git a/examples/bench.rs b/crates/toml_edit/examples/bench.rs
similarity index 100%
rename from examples/bench.rs
rename to crates/toml_edit/examples/bench.rs
diff --git a/examples/bench_easy_de.rs b/crates/toml_edit/examples/bench_easy_de.rs
similarity index 100%
rename from examples/bench_easy_de.rs
rename to crates/toml_edit/examples/bench_easy_de.rs
diff --git a/examples/bench_easy_value.rs b/crates/toml_edit/examples/bench_easy_value.rs
similarity index 100%
rename from examples/bench_easy_value.rs
rename to crates/toml_edit/examples/bench_easy_value.rs
diff --git a/examples/visit.rs b/crates/toml_edit/examples/visit.rs
similarity index 100%
rename from examples/visit.rs
rename to crates/toml_edit/examples/visit.rs
diff --git a/fuzz/.gitignore b/crates/toml_edit/fuzz/.gitignore
similarity index 100%
rename from fuzz/.gitignore
rename to crates/toml_edit/fuzz/.gitignore
diff --git a/fuzz/Cargo.toml b/crates/toml_edit/fuzz/Cargo.toml
similarity index 100%
rename from fuzz/Cargo.toml
rename to crates/toml_edit/fuzz/Cargo.toml
diff --git a/fuzz/README.md b/crates/toml_edit/fuzz/README.md
similarity index 100%
rename from fuzz/README.md
rename to crates/toml_edit/fuzz/README.md
diff --git a/fuzz/parse_document.rs b/crates/toml_edit/fuzz/parse_document.rs
similarity index 100%
rename from fuzz/parse_document.rs
rename to crates/toml_edit/fuzz/parse_document.rs
diff --git a/fuzz/seeds/0001 b/crates/toml_edit/fuzz/seeds/0001
similarity index 100%
rename from fuzz/seeds/0001
rename to crates/toml_edit/fuzz/seeds/0001
diff --git a/src/array.rs b/crates/toml_edit/src/array.rs
similarity index 100%
rename from src/array.rs
rename to crates/toml_edit/src/array.rs
diff --git a/src/array_of_tables.rs b/crates/toml_edit/src/array_of_tables.rs
similarity index 100%
rename from src/array_of_tables.rs
rename to crates/toml_edit/src/array_of_tables.rs
diff --git a/src/datetime.rs b/crates/toml_edit/src/datetime.rs
similarity index 100%
rename from src/datetime.rs
rename to crates/toml_edit/src/datetime.rs
diff --git a/src/de/array.rs b/crates/toml_edit/src/de/array.rs
similarity index 100%
rename from src/de/array.rs
rename to crates/toml_edit/src/de/array.rs
diff --git a/src/de/inline_table.rs b/crates/toml_edit/src/de/inline_table.rs
similarity index 100%
rename from src/de/inline_table.rs
rename to crates/toml_edit/src/de/inline_table.rs
diff --git a/src/de/item.rs b/crates/toml_edit/src/de/item.rs
similarity index 100%
rename from src/de/item.rs
rename to crates/toml_edit/src/de/item.rs
diff --git a/src/de/mod.rs b/crates/toml_edit/src/de/mod.rs
similarity index 100%
rename from src/de/mod.rs
rename to crates/toml_edit/src/de/mod.rs
diff --git a/src/de/table.rs b/crates/toml_edit/src/de/table.rs
similarity index 100%
rename from src/de/table.rs
rename to crates/toml_edit/src/de/table.rs
diff --git a/src/de/table_enum.rs b/crates/toml_edit/src/de/table_enum.rs
similarity index 100%
rename from src/de/table_enum.rs
rename to crates/toml_edit/src/de/table_enum.rs
diff --git a/src/de/value.rs b/crates/toml_edit/src/de/value.rs
similarity index 100%
rename from src/de/value.rs
rename to crates/toml_edit/src/de/value.rs
diff --git a/src/document.rs b/crates/toml_edit/src/document.rs
similarity index 100%
rename from src/document.rs
rename to crates/toml_edit/src/document.rs
diff --git a/src/easy/datetime.rs b/crates/toml_edit/src/easy/datetime.rs
similarity index 100%
rename from src/easy/datetime.rs
rename to crates/toml_edit/src/easy/datetime.rs
diff --git a/src/easy/macros.rs b/crates/toml_edit/src/easy/macros.rs
similarity index 100%
rename from src/easy/macros.rs
rename to crates/toml_edit/src/easy/macros.rs
diff --git a/src/easy/map.rs b/crates/toml_edit/src/easy/map.rs
similarity index 100%
rename from src/easy/map.rs
rename to crates/toml_edit/src/easy/map.rs
diff --git a/src/easy/mod.rs b/crates/toml_edit/src/easy/mod.rs
similarity index 100%
rename from src/easy/mod.rs
rename to crates/toml_edit/src/easy/mod.rs
diff --git a/src/easy/value.rs b/crates/toml_edit/src/easy/value.rs
similarity index 100%
rename from src/easy/value.rs
rename to crates/toml_edit/src/easy/value.rs
diff --git a/src/encode.rs b/crates/toml_edit/src/encode.rs
similarity index 100%
rename from src/encode.rs
rename to crates/toml_edit/src/encode.rs
diff --git a/src/index.rs b/crates/toml_edit/src/index.rs
similarity index 100%
rename from src/index.rs
rename to crates/toml_edit/src/index.rs
diff --git a/src/inline_table.rs b/crates/toml_edit/src/inline_table.rs
similarity index 100%
rename from src/inline_table.rs
rename to crates/toml_edit/src/inline_table.rs
diff --git a/src/internal_string.rs b/crates/toml_edit/src/internal_string.rs
similarity index 100%
rename from src/internal_string.rs
rename to crates/toml_edit/src/internal_string.rs
diff --git a/src/item.rs b/crates/toml_edit/src/item.rs
similarity index 100%
rename from src/item.rs
rename to crates/toml_edit/src/item.rs
diff --git a/src/key.rs b/crates/toml_edit/src/key.rs
similarity index 100%
rename from src/key.rs
rename to crates/toml_edit/src/key.rs
diff --git a/src/lib.rs b/crates/toml_edit/src/lib.rs
similarity index 100%
rename from src/lib.rs
rename to crates/toml_edit/src/lib.rs
diff --git a/src/parser/array.rs b/crates/toml_edit/src/parser/array.rs
similarity index 100%
rename from src/parser/array.rs
rename to crates/toml_edit/src/parser/array.rs
diff --git a/src/parser/datetime.rs b/crates/toml_edit/src/parser/datetime.rs
similarity index 100%
rename from src/parser/datetime.rs
rename to crates/toml_edit/src/parser/datetime.rs
diff --git a/src/parser/document.rs b/crates/toml_edit/src/parser/document.rs
similarity index 100%
rename from src/parser/document.rs
rename to crates/toml_edit/src/parser/document.rs
diff --git a/src/parser/errors.rs b/crates/toml_edit/src/parser/errors.rs
similarity index 100%
rename from src/parser/errors.rs
rename to crates/toml_edit/src/parser/errors.rs
diff --git a/src/parser/inline_table.rs b/crates/toml_edit/src/parser/inline_table.rs
similarity index 100%
rename from src/parser/inline_table.rs
rename to crates/toml_edit/src/parser/inline_table.rs
diff --git a/src/parser/key.rs b/crates/toml_edit/src/parser/key.rs
similarity index 100%
rename from src/parser/key.rs
rename to crates/toml_edit/src/parser/key.rs
diff --git a/src/parser/macros.rs b/crates/toml_edit/src/parser/macros.rs
similarity index 100%
rename from src/parser/macros.rs
rename to crates/toml_edit/src/parser/macros.rs
diff --git a/src/parser/mod.rs b/crates/toml_edit/src/parser/mod.rs
similarity index 100%
rename from src/parser/mod.rs
rename to crates/toml_edit/src/parser/mod.rs
diff --git a/src/parser/numbers.rs b/crates/toml_edit/src/parser/numbers.rs
similarity index 100%
rename from src/parser/numbers.rs
rename to crates/toml_edit/src/parser/numbers.rs
diff --git a/src/parser/strings.rs b/crates/toml_edit/src/parser/strings.rs
similarity index 100%
rename from src/parser/strings.rs
rename to crates/toml_edit/src/parser/strings.rs
diff --git a/src/parser/table.rs b/crates/toml_edit/src/parser/table.rs
similarity index 100%
rename from src/parser/table.rs
rename to crates/toml_edit/src/parser/table.rs
diff --git a/src/parser/trivia.rs b/crates/toml_edit/src/parser/trivia.rs
similarity index 100%
rename from src/parser/trivia.rs
rename to crates/toml_edit/src/parser/trivia.rs
diff --git a/src/parser/value.rs b/crates/toml_edit/src/parser/value.rs
similarity index 100%
rename from src/parser/value.rs
rename to crates/toml_edit/src/parser/value.rs
diff --git a/src/repr.rs b/crates/toml_edit/src/repr.rs
similarity index 100%
rename from src/repr.rs
rename to crates/toml_edit/src/repr.rs
diff --git a/src/ser/array.rs b/crates/toml_edit/src/ser/array.rs
similarity index 100%
rename from src/ser/array.rs
rename to crates/toml_edit/src/ser/array.rs
diff --git a/src/ser/item.rs b/crates/toml_edit/src/ser/item.rs
similarity index 100%
rename from src/ser/item.rs
rename to crates/toml_edit/src/ser/item.rs
diff --git a/src/ser/key.rs b/crates/toml_edit/src/ser/key.rs
similarity index 100%
rename from src/ser/key.rs
rename to crates/toml_edit/src/ser/key.rs
diff --git a/src/ser/mod.rs b/crates/toml_edit/src/ser/mod.rs
similarity index 100%
rename from src/ser/mod.rs
rename to crates/toml_edit/src/ser/mod.rs
diff --git a/src/ser/pretty.rs b/crates/toml_edit/src/ser/pretty.rs
similarity index 100%
rename from src/ser/pretty.rs
rename to crates/toml_edit/src/ser/pretty.rs
diff --git a/src/ser/table.rs b/crates/toml_edit/src/ser/table.rs
similarity index 100%
rename from src/ser/table.rs
rename to crates/toml_edit/src/ser/table.rs
diff --git a/src/table.rs b/crates/toml_edit/src/table.rs
similarity index 100%
rename from src/table.rs
rename to crates/toml_edit/src/table.rs
diff --git a/src/value.rs b/crates/toml_edit/src/value.rs
similarity index 100%
rename from src/value.rs
rename to crates/toml_edit/src/value.rs
diff --git a/src/visit.rs b/crates/toml_edit/src/visit.rs
similarity index 100%
rename from src/visit.rs
rename to crates/toml_edit/src/visit.rs
diff --git a/src/visit_mut.rs b/crates/toml_edit/src/visit_mut.rs
similarity index 100%
rename from src/visit_mut.rs
rename to crates/toml_edit/src/visit_mut.rs


Test patch:
diff --git a/crates/test-suite/Cargo.toml b/crates/test-suite/Cargo.toml
new file mode 100644
index 00000000..5843bdf7
--- /dev/null
+++ b/crates/test-suite/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "toml_test_suite"
+version = "0.0.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+publish = false
+edition = "2018"
+
+[[bench]]
+name = "linear"
+harness = false
+
+[dev-dependencies]
+bencher = "0.1"
+toml = { path = "../toml" }
+serde = { version = "1.0", features = ["derive"] }
+serde_derive = "1.0"
+serde_json = "1.0"
diff --git a/crates/test-suite/benches/linear.rs b/crates/test-suite/benches/linear.rs
new file mode 100644
index 00000000..15535955
--- /dev/null
+++ b/crates/test-suite/benches/linear.rs
@@ -0,0 +1,35 @@
+// Regressoion test for https://github.com/alexcrichton/toml-rs/issues/342
+
+use bencher::{benchmark_group, benchmark_main, black_box, Bencher};
+use toml::Value;
+
+fn parse(bench: &mut Bencher, entries: usize, f: impl Fn(usize) -> String) {
+    let mut s = String::new();
+    for i in 0..entries {
+        s += &f(i);
+        s += "entry = 42\n"
+    }
+    let s = black_box(s);
+    bench.iter(|| {
+        black_box(s.parse::<Value>().unwrap());
+    })
+}
+
+fn map_10(bench: &mut Bencher) {
+    parse(bench, 10, |i| format!("[header_no_{}]\n", i))
+}
+
+fn map_100(bench: &mut Bencher) {
+    parse(bench, 100, |i| format!("[header_no_{}]\n", i))
+}
+
+fn array_10(bench: &mut Bencher) {
+    parse(bench, 10, |_i| "[[header]]\n".to_owned())
+}
+
+fn array_100(bench: &mut Bencher) {
+    parse(bench, 100, |_i| "[[header]]\n".to_owned())
+}
+
+benchmark_group!(benches, map_10, map_100, array_10, array_100);
+benchmark_main!(benches);
diff --git a/crates/test-suite/tests/README.md b/crates/test-suite/tests/README.md
new file mode 100644
index 00000000..ebbc01cc
--- /dev/null
+++ b/crates/test-suite/tests/README.md
@@ -0,0 +1,1 @@
+Tests are from https://github.com/BurntSushi/toml-test
diff --git a/crates/test-suite/tests/backcompat.rs b/crates/test-suite/tests/backcompat.rs
new file mode 100644
index 00000000..ef3bd369
--- /dev/null
+++ b/crates/test-suite/tests/backcompat.rs
@@ -0,0 +1,59 @@
+extern crate serde;
+extern crate toml;
+
+use serde::de::Deserialize;
+
+macro_rules! bad {
+    ($toml:expr, $msg:expr) => {
+        match $toml.parse::<toml::Value>() {
+            Ok(s) => panic!("parsed to: {:#?}", s),
+            Err(e) => assert_eq!(e.to_string(), $msg),
+        }
+    };
+}
+
+#[test]
+fn newlines_after_tables() {
+    let s = "
+        [a] foo = 1
+        [[b]] foo = 1
+    ";
+    bad!(
+        s,
+        "expected newline, found an identifier at line 2 column 13"
+    );
+
+    let mut d = toml::de::Deserializer::new(s);
+    d.set_require_newline_after_table(false);
+    let value = toml::Value::deserialize(&mut d).unwrap();
+    assert_eq!(value["a"]["foo"].as_integer(), Some(1));
+    assert_eq!(value["b"][0]["foo"].as_integer(), Some(1));
+}
+
+#[test]
+fn allow_duplicate_after_longer() {
+    let s = "
+        [dependencies.openssl-sys]
+        version = 1
+
+        [dependencies]
+        libc = 1
+
+        [dependencies]
+        bitflags = 1
+    ";
+    bad!(
+        s,
+        "redefinition of table `dependencies` for key `dependencies` at line 8 column 9"
+    );
+
+    let mut d = toml::de::Deserializer::new(s);
+    d.set_allow_duplicate_after_longer_table(true);
+    let value = toml::Value::deserialize(&mut d).unwrap();
+    assert_eq!(
+        value["dependencies"]["openssl-sys"]["version"].as_integer(),
+        Some(1)
+    );
+    assert_eq!(value["dependencies"]["libc"].as_integer(), Some(1));
+    assert_eq!(value["dependencies"]["bitflags"].as_integer(), Some(1));
+}
diff --git a/crates/test-suite/tests/datetime.rs b/crates/test-suite/tests/datetime.rs
new file mode 100644
index 00000000..6c08748f
--- /dev/null
+++ b/crates/test-suite/tests/datetime.rs
@@ -0,0 +1,134 @@
+extern crate toml;
+
+use std::str::FromStr;
+
+macro_rules! bad {
+    ($toml:expr, $msg:expr) => {
+        match $toml.parse::<toml::Value>() {
+            Ok(s) => panic!("parsed to: {:#?}", s),
+            Err(e) => assert_eq!(e.to_string(), $msg),
+        }
+    };
+}
+
+#[test]
+fn times() {
+    fn dogood(s: &str, serialized: &str) {
+        let to_parse = format!("foo = {}", s);
+        let value = toml::Value::from_str(&to_parse).unwrap();
+        assert_eq!(value["foo"].as_datetime().unwrap().to_string(), serialized);
+    }
+    fn good(s: &str) {
+        dogood(s, s);
+        dogood(&s.replace("T", " "), s);
+        dogood(&s.replace("T", "t"), s);
+        dogood(&s.replace("Z", "z"), s);
+    }
+
+    good("1997-09-09T09:09:09Z");
+    good("1997-09-09T09:09:09+09:09");
+    good("1997-09-09T09:09:09-09:09");
+    good("1997-09-09T09:09:09");
+    good("1997-09-09");
+    dogood("1997-09-09 ", "1997-09-09");
+    dogood("1997-09-09 # comment", "1997-09-09");
+    good("09:09:09");
+    good("1997-09-09T09:09:09.09Z");
+    good("1997-09-09T09:09:09.09+09:09");
+    good("1997-09-09T09:09:09.09-09:09");
+    good("1997-09-09T09:09:09.09");
+    good("09:09:09.09");
+}
+
+#[test]
+fn bad_times() {
+    bad!(
+        "foo = 199-09-09",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 199709-09",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-9-09",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-9",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-0909:09:09",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.",
+        "invalid date at line 1 column 7"
+    );
+    bad!(
+        "foo = T",
+        "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
+    );
+    bad!(
+        "foo = T.",
+        "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
+    );
+    bad!(
+        "foo = TZ",
+        "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.09+",
+        "invalid date at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.09+09",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.09+09:9",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.09+0909",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.09-",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.09-09",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.09-09:9",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T09:09:09.09-0909",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+
+    bad!(
+        "foo = 1997-00-09T09:09:09.09Z",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-00T09:09:09.09Z",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T30:09:09.09Z",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T12:69:09.09Z",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 1997-09-09T12:09:69.09Z",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+}
diff --git a/crates/test-suite/tests/de-errors.rs b/crates/test-suite/tests/de-errors.rs
new file mode 100644
index 00000000..76664895
--- /dev/null
+++ b/crates/test-suite/tests/de-errors.rs
@@ -0,0 +1,351 @@
+extern crate serde;
+extern crate toml;
+
+use serde::{de, Deserialize};
+use std::fmt;
+
+macro_rules! bad {
+    ($toml:expr, $ty:ty, $msg:expr) => {
+        match toml::from_str::<$ty>($toml) {
+            Ok(s) => panic!("parsed to: {:#?}", s),
+            Err(e) => assert_eq!(e.to_string(), $msg),
+        }
+    };
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct Parent<T> {
+    p_a: T,
+    p_b: Vec<Child<T>>,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+#[serde(deny_unknown_fields)]
+struct Child<T> {
+    c_a: T,
+    c_b: T,
+}
+
+#[derive(Debug, PartialEq)]
+enum CasedString {
+    Lowercase(String),
+    Uppercase(String),
+}
+
+impl<'de> de::Deserialize<'de> for CasedString {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: de::Deserializer<'de>,
+    {
+        struct CasedStringVisitor;
+
+        impl<'de> de::Visitor<'de> for CasedStringVisitor {
+            type Value = CasedString;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("a string")
+            }
+
+            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                if s.is_empty() {
+                    Err(de::Error::invalid_length(0, &"a non-empty string"))
+                } else if s.chars().all(|x| x.is_ascii_lowercase()) {
+                    Ok(CasedString::Lowercase(s.to_string()))
+                } else if s.chars().all(|x| x.is_ascii_uppercase()) {
+                    Ok(CasedString::Uppercase(s.to_string()))
+                } else {
+                    Err(de::Error::invalid_value(
+                        de::Unexpected::Str(s),
+                        &"all lowercase or all uppercase",
+                    ))
+                }
+            }
+        }
+
+        deserializer.deserialize_any(CasedStringVisitor)
+    }
+}
+
+#[test]
+fn custom_errors() {
+    toml::from_str::<Parent<CasedString>>(
+        "
+            p_a = 'a'
+            p_b = [{c_a = 'a', c_b = 'c'}]
+        ",
+    )
+    .unwrap();
+
+    // Custom error at p_b value.
+    bad!(
+        "
+            p_a = ''
+                # ^
+        ",
+        Parent<CasedString>,
+        "invalid length 0, expected a non-empty string for key `p_a` at line 2 column 19"
+    );
+
+    // Missing field in table.
+    bad!(
+        "
+            p_a = 'a'
+          # ^
+        ",
+        Parent<CasedString>,
+        "missing field `p_b` at line 1 column 1"
+    );
+
+    // Invalid type in p_b.
+    bad!(
+        "
+            p_a = 'a'
+            p_b = 1
+                # ^
+        ",
+        Parent<CasedString>,
+        "invalid type: integer `1`, expected a sequence for key `p_b` at line 3 column 19"
+    );
+
+    // Sub-table in Vec is missing a field.
+    bad!(
+        "
+            p_a = 'a'
+            p_b = [
+                {c_a = 'a'}
+              # ^
+            ]
+        ",
+        Parent<CasedString>,
+        "missing field `c_b` for key `p_b` at line 4 column 17"
+    );
+
+    // Sub-table in Vec has a field with a bad value.
+    bad!(
+        "
+            p_a = 'a'
+            p_b = [
+                {c_a = 'a', c_b = '*'}
+                                # ^
+            ]
+        ",
+        Parent<CasedString>,
+        "invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b` at line 4 column 35"
+    );
+
+    // Sub-table in Vec is missing a field.
+    bad!(
+        "
+            p_a = 'a'
+            p_b = [
+                {c_a = 'a', c_b = 'b'},
+                {c_a = 'aa'}
+              # ^
+            ]
+        ",
+        Parent<CasedString>,
+        "missing field `c_b` for key `p_b` at line 5 column 17"
+    );
+
+    // Sub-table in the middle of a Vec is missing a field.
+    bad!(
+        "
+            p_a = 'a'
+            p_b = [
+                {c_a = 'a', c_b = 'b'},
+                {c_a = 'aa'},
+              # ^
+                {c_a = 'aaa', c_b = 'bbb'},
+            ]
+        ",
+        Parent<CasedString>,
+        "missing field `c_b` for key `p_b` at line 5 column 17"
+    );
+
+    // Sub-table in the middle of a Vec has a field with a bad value.
+    bad!(
+        "
+            p_a = 'a'
+            p_b = [
+                {c_a = 'a', c_b = 'b'},
+                {c_a = 'aa', c_b = 1},
+                                 # ^
+                {c_a = 'aaa', c_b = 'bbb'},
+            ]
+        ",
+        Parent<CasedString>,
+        "invalid type: integer `1`, expected a string for key `p_b` at line 5 column 36"
+    );
+
+    // Sub-table in the middle of a Vec has an extra field.
+    // FIXME: This location could be better.
+    bad!(
+        "
+            p_a = 'a'
+            p_b = [
+                {c_a = 'a', c_b = 'b'},
+                {c_a = 'aa', c_b = 'bb', c_d = 'd'},
+              # ^
+                {c_a = 'aaa', c_b = 'bbb'},
+                {c_a = 'aaaa', c_b = 'bbbb'},
+            ]
+        ",
+        Parent<CasedString>,
+        "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 5 column 17"
+    );
+
+    // Sub-table in the middle of a Vec is missing a field.
+    // FIXME: This location is pretty off.
+    bad!(
+        "
+            p_a = 'a'
+            [[p_b]]
+            c_a = 'a'
+            c_b = 'b'
+            [[p_b]]
+            c_a = 'aa'
+            # c_b = 'bb' # <- missing field
+            [[p_b]]
+            c_a = 'aaa'
+            c_b = 'bbb'
+            [[p_b]]
+          # ^
+            c_a = 'aaaa'
+            c_b = 'bbbb'
+        ",
+        Parent<CasedString>,
+        "missing field `c_b` for key `p_b` at line 12 column 13"
+    );
+
+    // Sub-table in the middle of a Vec has a field with a bad value.
+    bad!(
+        "
+            p_a = 'a'
+            [[p_b]]
+            c_a = 'a'
+            c_b = 'b'
+            [[p_b]]
+            c_a = 'aa'
+            c_b = '*'
+                # ^
+            [[p_b]]
+            c_a = 'aaa'
+            c_b = 'bbb'
+        ",
+        Parent<CasedString>,
+        "invalid value: string \"*\", expected all lowercase or all uppercase for key `p_b.c_b` at line 8 column 19"
+    );
+
+    // Sub-table in the middle of a Vec has an extra field.
+    // FIXME: This location is pretty off.
+    bad!(
+        "
+            p_a = 'a'
+            [[p_b]]
+            c_a = 'a'
+            c_b = 'b'
+            [[p_b]]
+            c_a = 'aa'
+            c_d = 'dd' # unknown field
+            [[p_b]]
+            c_a = 'aaa'
+            c_b = 'bbb'
+            [[p_b]]
+          # ^
+            c_a = 'aaaa'
+            c_b = 'bbbb'
+        ",
+        Parent<CasedString>,
+        "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 12 column 13"
+    );
+}
+
+#[test]
+fn serde_derive_deserialize_errors() {
+    bad!(
+        "
+            p_a = ''
+          # ^
+        ",
+        Parent<String>,
+        "missing field `p_b` at line 1 column 1"
+    );
+
+    bad!(
+        "
+            p_a = ''
+            p_b = [
+                {c_a = ''}
+              # ^
+            ]
+        ",
+        Parent<String>,
+        "missing field `c_b` for key `p_b` at line 4 column 17"
+    );
+
+    bad!(
+        "
+            p_a = ''
+            p_b = [
+                {c_a = '', c_b = 1}
+                               # ^
+            ]
+        ",
+        Parent<String>,
+        "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34"
+    );
+
+    // FIXME: This location could be better.
+    bad!(
+        "
+            p_a = ''
+            p_b = [
+                {c_a = '', c_b = '', c_d = ''},
+              # ^
+            ]
+        ",
+        Parent<String>,
+        "unknown field `c_d`, expected `c_a` or `c_b` for key `p_b` at line 4 column 17"
+    );
+
+    bad!(
+        "
+            p_a = 'a'
+            p_b = [
+                {c_a = '', c_b = 1, c_d = ''},
+                               # ^
+            ]
+        ",
+        Parent<String>,
+        "invalid type: integer `1`, expected a string for key `p_b` at line 4 column 34"
+    );
+}
+
+#[test]
+fn error_handles_crlf() {
+    bad!(
+        "\r\n\
+         [t1]\r\n\
+         [t2]\r\n\
+         a = 1\r\n\
+         a = 2\r\n\
+         ",
+        toml::Value,
+        "duplicate key: `a` for key `t2` at line 3 column 1"
+    );
+
+    // Should be the same as above.
+    bad!(
+        "\n\
+         [t1]\n\
+         [t2]\n\
+         a = 1\n\
+         a = 2\n\
+         ",
+        toml::Value,
+        "duplicate key: `a` for key `t2` at line 3 column 1"
+    );
+}
diff --git a/crates/test-suite/tests/display-tricky.rs b/crates/test-suite/tests/display-tricky.rs
new file mode 100644
index 00000000..0daa10ed
--- /dev/null
+++ b/crates/test-suite/tests/display-tricky.rs
@@ -0,0 +1,56 @@
+extern crate toml;
+#[macro_use]
+extern crate serde_derive;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Recipe {
+    pub name: String,
+    pub description: Option<String>,
+    #[serde(default)]
+    pub modules: Vec<Modules>,
+    #[serde(default)]
+    pub packages: Vec<Packages>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Modules {
+    pub name: String,
+    pub version: Option<String>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Packages {
+    pub name: String,
+    pub version: Option<String>,
+}
+
+#[test]
+fn both_ends() {
+    let recipe_works = toml::from_str::<Recipe>(
+        r#"
+        name = "testing"
+        description = "example"
+        modules = []
+
+        [[packages]]
+        name = "base"
+    "#,
+    )
+    .unwrap();
+    toml::to_string(&recipe_works).unwrap();
+
+    let recipe_fails = toml::from_str::<Recipe>(
+        r#"
+        name = "testing"
+        description = "example"
+        packages = []
+
+        [[modules]]
+        name = "base"
+    "#,
+    )
+    .unwrap();
+
+    let recipe_toml = toml::Value::try_from(recipe_fails).unwrap();
+    recipe_toml.to_string();
+}
diff --git a/crates/test-suite/tests/display.rs b/crates/test-suite/tests/display.rs
new file mode 100644
index 00000000..5d9f44a5
--- /dev/null
+++ b/crates/test-suite/tests/display.rs
@@ -0,0 +1,117 @@
+extern crate toml;
+
+use toml::map::Map;
+use toml::Value::{Array, Boolean, Float, Integer, String, Table};
+
+macro_rules! map( ($($k:expr => $v:expr),*) => ({
+    let mut _m = Map::new();
+    $(_m.insert($k.to_string(), $v);)*
+    _m
+}) );
+
+#[test]
+fn simple_show() {
+    assert_eq!(String("foo".to_string()).to_string(), "\"foo\"");
+    assert_eq!(Integer(10).to_string(), "10");
+    assert_eq!(Float(10.0).to_string(), "10.0");
+    assert_eq!(Float(2.4).to_string(), "2.4");
+    assert_eq!(Boolean(true).to_string(), "true");
+    assert_eq!(Array(vec![]).to_string(), "[]");
+    assert_eq!(Array(vec![Integer(1), Integer(2)]).to_string(), "[1, 2]");
+}
+
+#[test]
+fn table() {
+    assert_eq!(Table(map! {}).to_string(), "");
+    assert_eq!(
+        Table(map! {
+        "test" => Integer(2),
+        "test2" => Integer(3) })
+        .to_string(),
+        "test = 2\ntest2 = 3\n"
+    );
+    assert_eq!(
+        Table(map! {
+             "test" => Integer(2),
+             "test2" => Table(map! {
+                 "test" => String("wut".to_string())
+             })
+        })
+        .to_string(),
+        "test = 2\n\
+         \n\
+         [test2]\n\
+         test = \"wut\"\n"
+    );
+    assert_eq!(
+        Table(map! {
+             "test" => Integer(2),
+             "test2" => Table(map! {
+                 "test" => String("wut".to_string())
+             })
+        })
+        .to_string(),
+        "test = 2\n\
+         \n\
+         [test2]\n\
+         test = \"wut\"\n"
+    );
+    assert_eq!(
+        Table(map! {
+             "test" => Integer(2),
+             "test2" => Array(vec![Table(map! {
+                 "test" => String("wut".to_string())
+             })])
+        })
+        .to_string(),
+        "test = 2\n\
+         \n\
+         [[test2]]\n\
+         test = \"wut\"\n"
+    );
+    assert_eq!(
+        Table(map! {
+             "foo.bar" => Integer(2),
+             "foo\"bar" => Integer(2)
+        })
+        .to_string(),
+        "\"foo\\\"bar\" = 2\n\
+         \"foo.bar\" = 2\n"
+    );
+    assert_eq!(
+        Table(map! {
+             "test" => Integer(2),
+             "test2" => Array(vec![Table(map! {
+                 "test" => Array(vec![Integer(2)])
+             })])
+        })
+        .to_string(),
+        "test = 2\n\
+         \n\
+         [[test2]]\n\
+         test = [2]\n"
+    );
+    let table = Table(map! {
+        "test" => Integer(2),
+        "test2" => Array(vec![Table(map! {
+            "test" => Array(vec![Array(vec![Integer(2), Integer(3)]),
+            Array(vec![String("foo".to_string()), String("bar".to_string())])])
+        })])
+    });
+    assert_eq!(
+        table.to_string(),
+        "test = 2\n\
+         \n\
+         [[test2]]\n\
+         test = [[2, 3], [\"foo\", \"bar\"]]\n"
+    );
+    assert_eq!(
+        Table(map! {
+             "test" => Array(vec![Integer(2)]),
+             "test2" => Integer(2)
+        })
+        .to_string(),
+        "test = [2]\n\
+         test2 = 2\n"
+    );
+}
diff --git a/crates/test-suite/tests/float.rs b/crates/test-suite/tests/float.rs
new file mode 100644
index 00000000..66414ea1
--- /dev/null
+++ b/crates/test-suite/tests/float.rs
@@ -0,0 +1,82 @@
+extern crate toml;
+#[macro_use]
+extern crate serde_derive;
+
+use toml::Value;
+
+#[rustfmt::skip] // appears to be a bug in rustfmt to make this converge...
+macro_rules! float_inf_tests {
+    ($ty:ty) => {{
+        #[derive(Serialize, Deserialize)]
+        struct S {
+            sf1: $ty,
+            sf2: $ty,
+            sf3: $ty,
+            sf4: $ty,
+            sf5: $ty,
+            sf6: $ty,
+            sf7: $ty,
+            sf8: $ty,
+        }
+        let inf: S = toml::from_str(
+            r"
+        # infinity
+        sf1 = inf  # positive infinity
+        sf2 = +inf # positive infinity
+        sf3 = -inf # negative infinity
+
+        # not a number
+        sf4 = nan  # actual sNaN/qNaN encoding is implementation specific
+        sf5 = +nan # same as `nan`
+        sf6 = -nan # valid, actual encoding is implementation specific
+
+        # zero
+        sf7 = +0.0
+        sf8 = -0.0
+        ",
+        )
+        .expect("Parse infinities.");
+
+        assert!(inf.sf1.is_infinite());
+        assert!(inf.sf1.is_sign_positive());
+        assert!(inf.sf2.is_infinite());
+        assert!(inf.sf2.is_sign_positive());
+        assert!(inf.sf3.is_infinite());
+        assert!(inf.sf3.is_sign_negative());
+
+        assert!(inf.sf4.is_nan());
+        assert!(inf.sf4.is_sign_positive());
+        assert!(inf.sf5.is_nan());
+        assert!(inf.sf5.is_sign_positive());
+        assert!(inf.sf6.is_nan());
+        assert!(inf.sf6.is_sign_negative());
+
+        assert_eq!(inf.sf7, 0.0);
+        assert!(inf.sf7.is_sign_positive());
+        assert_eq!(inf.sf8, 0.0);
+        assert!(inf.sf8.is_sign_negative());
+
+        let s = toml::to_string(&inf).unwrap();
+        assert_eq!(
+            s,
+            "\
+sf1 = inf
+sf2 = inf
+sf3 = -inf
+sf4 = nan
+sf5 = nan
+sf6 = -nan
+sf7 = 0.0
+sf8 = -0.0
+"
+        );
+
+        toml::from_str::<Value>(&s).expect("roundtrip");
+    }};
+}
+
+#[test]
+fn float_inf() {
+    float_inf_tests!(f32);
+    float_inf_tests!(f64);
+}
diff --git a/crates/test-suite/tests/formatting.rs b/crates/test-suite/tests/formatting.rs
new file mode 100644
index 00000000..8e15ec24
--- /dev/null
+++ b/crates/test-suite/tests/formatting.rs
@@ -0,0 +1,56 @@
+#[macro_use]
+extern crate serde_derive;
+extern crate toml;
+
+use toml::to_string;
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+struct User {
+    pub name: String,
+    pub surname: String,
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+struct Users {
+    pub user: Vec<User>,
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+struct TwoUsers {
+    pub user0: User,
+    pub user1: User,
+}
+
+#[test]
+fn no_unnecessary_newlines_array() {
+    assert!(!to_string(&Users {
+        user: vec![
+            User {
+                name: "John".to_string(),
+                surname: "Doe".to_string(),
+            },
+            User {
+                name: "Jane".to_string(),
+                surname: "Dough".to_string(),
+            },
+        ],
+    })
+    .unwrap()
+    .starts_with("\n"));
+}
+
+#[test]
+fn no_unnecessary_newlines_table() {
+    assert!(!to_string(&TwoUsers {
+        user0: User {
+            name: "John".to_string(),
+            surname: "Doe".to_string(),
+        },
+        user1: User {
+            name: "Jane".to_string(),
+            surname: "Dough".to_string(),
+        },
+    })
+    .unwrap()
+    .starts_with("\n"));
+}
diff --git a/crates/test-suite/tests/invalid-encoder/array-mixed-types-ints-and-floats.json b/crates/test-suite/tests/invalid-encoder/array-mixed-types-ints-and-floats.json
new file mode 100644
index 00000000..2d42ead6
--- /dev/null
+++ b/crates/test-suite/tests/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.1"
+            }
+        ]
+    }
+}
diff --git a/crates/test-suite/tests/invalid-misc.rs b/crates/test-suite/tests/invalid-misc.rs
new file mode 100644
index 00000000..7a101928
--- /dev/null
+++ b/crates/test-suite/tests/invalid-misc.rs
@@ -0,0 +1,51 @@
+extern crate toml;
+
+macro_rules! bad {
+    ($toml:expr, $msg:expr) => {
+        match $toml.parse::<toml::Value>() {
+            Ok(s) => panic!("parsed to: {:#?}", s),
+            Err(e) => assert_eq!(e.to_string(), $msg),
+        }
+    };
+}
+
+#[test]
+fn bad() {
+    bad!("a = 01", "invalid number at line 1 column 6");
+    bad!("a = 1__1", "invalid number at line 1 column 5");
+    bad!("a = 1_", "invalid number at line 1 column 5");
+    bad!("''", "expected an equals, found eof at line 1 column 3");
+    bad!("a = 9e99999", "invalid number at line 1 column 5");
+
+    bad!(
+        "a = \"\u{7f}\"",
+        "invalid character in string: `\\u{7f}` at line 1 column 6"
+    );
+    bad!(
+        "a = '\u{7f}'",
+        "invalid character in string: `\\u{7f}` at line 1 column 6"
+    );
+
+    bad!("a = -0x1", "invalid number at line 1 column 5");
+    bad!(
+        "a = 0x-1",
+        "failed to parse datetime for key `a` at line 1 column 5"
+    );
+
+    // Dotted keys.
+    bad!(
+        "a.b.c = 1
+         a.b = 2
+        ",
+        "duplicate key: `b` for key `a` at line 1 column 9"
+    );
+    bad!(
+        "a = 1
+         a.b = 2",
+        "dotted key attempted to extend non-table type at line 1 column 5"
+    );
+    bad!(
+        "a = {k1 = 1, k1.name = \"joe\"}",
+        "dotted key attempted to extend non-table type at line 1 column 11"
+    );
+}
diff --git a/crates/test-suite/tests/invalid.rs b/crates/test-suite/tests/invalid.rs
new file mode 100644
index 00000000..f04d860d
--- /dev/null
+++ b/crates/test-suite/tests/invalid.rs
@@ -0,0 +1,226 @@
+extern crate toml;
+
+macro_rules! bad {
+    ($toml:expr, $msg:expr) => {
+        match $toml.parse::<toml::Value>() {
+            Ok(s) => panic!("parsed to: {:#?}", s),
+            Err(e) => assert_eq!(e.to_string(), $msg),
+        }
+    };
+}
+
+macro_rules! test( ($name:ident, $s:expr, $msg:expr) => (
+    #[test]
+    fn $name() { bad!($s, $msg); }
+) );
+
+test!(
+    datetime_malformed_no_leads,
+    include_str!("invalid/datetime-malformed-no-leads.toml"),
+    "failed to parse datetime for key `no-leads` at line 1 column 12"
+);
+test!(
+    datetime_malformed_no_secs,
+    include_str!("invalid/datetime-malformed-no-secs.toml"),
+    "expected a colon, found a newline at line 1 column 28"
+);
+test!(
+    datetime_malformed_no_t,
+    include_str!("invalid/datetime-malformed-no-t.toml"),
+    "failed to parse datetime for key `no-t` at line 1 column 8"
+);
+test!(
+    datetime_malformed_with_milli,
+    include_str!("invalid/datetime-malformed-with-milli.toml"),
+    "failed to parse datetime for key `with-milli` at line 1 column 14"
+);
+test!(
+    duplicate_key_table,
+    include_str!("invalid/duplicate-key-table.toml"),
+    "duplicate key: `type` for key `fruit` at line 4 column 1"
+);
+test!(
+    duplicate_keys,
+    include_str!("invalid/duplicate-keys.toml"),
+    "duplicate key: `dupe` at line 1 column 1"
+);
+test!(
+    duplicate_table,
+    include_str!("invalid/duplicate-table.toml"),
+    "redefinition of table `dependencies` for key `dependencies` at line 7 column 1"
+);
+test!(
+    duplicate_tables,
+    include_str!("invalid/duplicate-tables.toml"),
+    "redefinition of table `a` for key `a` at line 2 column 1"
+);
+test!(
+    empty_implicit_table,
+    include_str!("invalid/empty-implicit-table.toml"),
+    "expected a table key, found a period at line 1 column 10"
+);
+test!(
+    empty_table,
+    include_str!("invalid/empty-table.toml"),
+    "expected a table key, found a right bracket at line 1 column 2"
+);
+test!(
+    float_no_leading_zero,
+    include_str!("invalid/float-no-leading-zero.toml"),
+    "expected a value, found a period at line 1 column 10"
+);
+test!(
+    float_no_suffix,
+    include_str!("invalid/float-no-suffix.toml"),
+    "invalid number at line 1 column 5"
+);
+test!(
+    float_no_trailing_digits,
+    include_str!("invalid/float-no-trailing-digits.toml"),
+    "invalid number at line 1 column 12"
+);
+test!(
+    key_after_array,
+    include_str!("invalid/key-after-array.toml"),
+    "expected newline, found an identifier at line 1 column 14"
+);
+test!(
+    key_after_table,
+    include_str!("invalid/key-after-table.toml"),
+    "expected newline, found an identifier at line 1 column 11"
+);
+test!(
+    key_empty,
+    include_str!("invalid/key-empty.toml"),
+    "expected a table key, found an equals at line 1 column 2"
+);
+test!(
+    key_hash,
+    include_str!("invalid/key-hash.toml"),
+    "expected an equals, found a comment at line 1 column 2"
+);
+test!(
+    key_newline,
+    include_str!("invalid/key-newline.toml"),
+    "expected an equals, found a newline at line 1 column 2"
+);
+test!(
+    key_open_bracket,
+    include_str!("invalid/key-open-bracket.toml"),
+    "expected a right bracket, found an equals at line 1 column 6"
+);
+test!(
+    key_single_open_bracket,
+    include_str!("invalid/key-single-open-bracket.toml"),
+    "expected a table key, found eof at line 1 column 2"
+);
+test!(
+    key_space,
+    include_str!("invalid/key-space.toml"),
+    "expected an equals, found an identifier at line 1 column 3"
+);
+test!(
+    key_start_bracket,
+    include_str!("invalid/key-start-bracket.toml"),
+    "expected a right bracket, found an equals at line 2 column 6"
+);
+test!(
+    key_two_equals,
+    include_str!("invalid/key-two-equals.toml"),
+    "expected a value, found an equals at line 1 column 6"
+);
+test!(
+    string_bad_byte_escape,
+    include_str!("invalid/string-bad-byte-escape.toml"),
+    "invalid escape character in string: `x` at line 1 column 13"
+);
+test!(
+    string_bad_escape,
+    include_str!("invalid/string-bad-escape.toml"),
+    "invalid escape character in string: `a` at line 1 column 42"
+);
+test!(
+    string_bad_line_ending_escape,
+    include_str!("invalid/string-bad-line-ending-escape.toml"),
+    "invalid escape character in string: ` ` at line 2 column 79"
+);
+test!(
+    string_byte_escapes,
+    include_str!("invalid/string-byte-escapes.toml"),
+    "invalid escape character in string: `x` at line 1 column 12"
+);
+test!(
+    string_no_close,
+    include_str!("invalid/string-no-close.toml"),
+    "newline in string found at line 1 column 42"
+);
+test!(
+    table_array_implicit,
+    include_str!("invalid/table-array-implicit.toml"),
+    "table redefined as array for key `albums` at line 13 column 1"
+);
+test!(
+    table_array_malformed_bracket,
+    include_str!("invalid/table-array-malformed-bracket.toml"),
+    "expected a right bracket, found a newline at line 1 column 10"
+);
+test!(
+    table_array_malformed_empty,
+    include_str!("invalid/table-array-malformed-empty.toml"),
+    "expected a table key, found a right bracket at line 1 column 3"
+);
+test!(
+    table_empty,
+    include_str!("invalid/table-empty.toml"),
+    "expected a table key, found a right bracket at line 1 column 2"
+);
+test!(
+    table_nested_brackets_close,
+    include_str!("invalid/table-nested-brackets-close.toml"),
+    "expected newline, found an identifier at line 1 column 4"
+);
+test!(
+    table_nested_brackets_open,
+    include_str!("invalid/table-nested-brackets-open.toml"),
+    "expected a right bracket, found a left bracket at line 1 column 3"
+);
+test!(
+    table_whitespace,
+    include_str!("invalid/table-whitespace.toml"),
+    "expected a right bracket, found an identifier at line 1 column 10"
+);
+test!(
+    table_with_pound,
+    include_str!("invalid/table-with-pound.toml"),
+    "expected a right bracket, found a comment at line 1 column 5"
+);
+test!(
+    text_after_array_entries,
+    include_str!("invalid/text-after-array-entries.toml"),
+    "invalid TOML value, did you mean to use a quoted string? at line 2 column 46"
+);
+test!(
+    text_after_integer,
+    include_str!("invalid/text-after-integer.toml"),
+    "expected newline, found an identifier at line 1 column 13"
+);
+test!(
+    text_after_string,
+    include_str!("invalid/text-after-string.toml"),
+    "expected newline, found an identifier at line 1 column 41"
+);
+test!(
+    text_after_table,
+    include_str!("invalid/text-after-table.toml"),
+    "expected newline, found an identifier at line 1 column 9"
+);
+test!(
+    text_before_array_separator,
+    include_str!("invalid/text-before-array-separator.toml"),
+    "expected a right bracket, found an identifier at line 2 column 46"
+);
+test!(
+    text_in_array,
+    include_str!("invalid/text-in-array.toml"),
+    "invalid TOML value, did you mean to use a quoted string? at line 3 column 3"
+);
diff --git a/tests/fixtures/invalid/datetime-malformed-no-leads.toml b/crates/test-suite/tests/invalid/datetime-malformed-no-leads.toml
similarity index 100%
rename from tests/fixtures/invalid/datetime-malformed-no-leads.toml
rename to crates/test-suite/tests/invalid/datetime-malformed-no-leads.toml
diff --git a/tests/fixtures/invalid/datetime-malformed-no-secs.toml b/crates/test-suite/tests/invalid/datetime-malformed-no-secs.toml
similarity index 100%
rename from tests/fixtures/invalid/datetime-malformed-no-secs.toml
rename to crates/test-suite/tests/invalid/datetime-malformed-no-secs.toml
diff --git a/tests/fixtures/invalid/datetime-malformed-no-t.toml b/crates/test-suite/tests/invalid/datetime-malformed-no-t.toml
similarity index 100%
rename from tests/fixtures/invalid/datetime-malformed-no-t.toml
rename to crates/test-suite/tests/invalid/datetime-malformed-no-t.toml
diff --git a/tests/fixtures/invalid/datetime-malformed-with-milli.toml b/crates/test-suite/tests/invalid/datetime-malformed-with-milli.toml
similarity index 100%
rename from tests/fixtures/invalid/datetime-malformed-with-milli.toml
rename to crates/test-suite/tests/invalid/datetime-malformed-with-milli.toml
diff --git a/tests/fixtures/invalid/duplicate-key-table.toml b/crates/test-suite/tests/invalid/duplicate-key-table.toml
similarity index 100%
rename from tests/fixtures/invalid/duplicate-key-table.toml
rename to crates/test-suite/tests/invalid/duplicate-key-table.toml
diff --git a/tests/fixtures/invalid/duplicate-keys.toml b/crates/test-suite/tests/invalid/duplicate-keys.toml
similarity index 100%
rename from tests/fixtures/invalid/duplicate-keys.toml
rename to crates/test-suite/tests/invalid/duplicate-keys.toml
diff --git a/crates/test-suite/tests/invalid/duplicate-table.toml b/crates/test-suite/tests/invalid/duplicate-table.toml
new file mode 100644
index 00000000..5bd2571e
--- /dev/null
+++ b/crates/test-suite/tests/invalid/duplicate-table.toml
@@ -0,0 +1,8 @@
+[dependencies.openssl-sys]
+version = "0.5.2"
+
+[dependencies]
+libc = "0.1"
+
+[dependencies]
+bitflags = "0.1.1"
diff --git a/tests/fixtures/invalid/duplicate-tables.toml b/crates/test-suite/tests/invalid/duplicate-tables.toml
similarity index 100%
rename from tests/fixtures/invalid/duplicate-tables.toml
rename to crates/test-suite/tests/invalid/duplicate-tables.toml
diff --git a/tests/fixtures/invalid/empty-implicit-table.toml b/crates/test-suite/tests/invalid/empty-implicit-table.toml
similarity index 100%
rename from tests/fixtures/invalid/empty-implicit-table.toml
rename to crates/test-suite/tests/invalid/empty-implicit-table.toml
diff --git a/tests/fixtures/invalid/empty-table.toml b/crates/test-suite/tests/invalid/empty-table.toml
similarity index 100%
rename from tests/fixtures/invalid/empty-table.toml
rename to crates/test-suite/tests/invalid/empty-table.toml
diff --git a/tests/fixtures/invalid/float-no-leading-zero.toml b/crates/test-suite/tests/invalid/float-no-leading-zero.toml
similarity index 100%
rename from tests/fixtures/invalid/float-no-leading-zero.toml
rename to crates/test-suite/tests/invalid/float-no-leading-zero.toml
diff --git a/crates/test-suite/tests/invalid/float-no-suffix.toml b/crates/test-suite/tests/invalid/float-no-suffix.toml
new file mode 100644
index 00000000..76106de7
--- /dev/null
+++ b/crates/test-suite/tests/invalid/float-no-suffix.toml
@@ -0,0 +1,1 @@
+a = 1.2f
diff --git a/tests/fixtures/invalid/float-no-trailing-digits.toml b/crates/test-suite/tests/invalid/float-no-trailing-digits.toml
similarity index 100%
rename from tests/fixtures/invalid/float-no-trailing-digits.toml
rename to crates/test-suite/tests/invalid/float-no-trailing-digits.toml
diff --git a/tests/fixtures/invalid/key-after-array.toml b/crates/test-suite/tests/invalid/key-after-array.toml
similarity index 100%
rename from tests/fixtures/invalid/key-after-array.toml
rename to crates/test-suite/tests/invalid/key-after-array.toml
diff --git a/crates/test-suite/tests/invalid/key-after-table.toml b/crates/test-suite/tests/invalid/key-after-table.toml
new file mode 100644
index 00000000..4bc82136
--- /dev/null
+++ b/crates/test-suite/tests/invalid/key-after-table.toml
@@ -0,0 +1,1 @@
+[history] guard = "sleeping"
diff --git a/tests/fixtures/invalid/key-empty.toml b/crates/test-suite/tests/invalid/key-empty.toml
similarity index 100%
rename from tests/fixtures/invalid/key-empty.toml
rename to crates/test-suite/tests/invalid/key-empty.toml
diff --git a/tests/fixtures/invalid/key-hash.toml b/crates/test-suite/tests/invalid/key-hash.toml
similarity index 100%
rename from tests/fixtures/invalid/key-hash.toml
rename to crates/test-suite/tests/invalid/key-hash.toml
diff --git a/tests/fixtures/invalid/key-newline.toml b/crates/test-suite/tests/invalid/key-newline.toml
similarity index 100%
rename from tests/fixtures/invalid/key-newline.toml
rename to crates/test-suite/tests/invalid/key-newline.toml
diff --git a/tests/fixtures/invalid/key-open-bracket.toml b/crates/test-suite/tests/invalid/key-open-bracket.toml
similarity index 100%
rename from tests/fixtures/invalid/key-open-bracket.toml
rename to crates/test-suite/tests/invalid/key-open-bracket.toml
diff --git a/tests/fixtures/invalid/key-single-open-bracket.toml b/crates/test-suite/tests/invalid/key-single-open-bracket.toml
similarity index 100%
rename from tests/fixtures/invalid/key-single-open-bracket.toml
rename to crates/test-suite/tests/invalid/key-single-open-bracket.toml
diff --git a/tests/fixtures/invalid/key-space.toml b/crates/test-suite/tests/invalid/key-space.toml
similarity index 100%
rename from tests/fixtures/invalid/key-space.toml
rename to crates/test-suite/tests/invalid/key-space.toml
diff --git a/tests/fixtures/invalid/key-start-bracket.toml b/crates/test-suite/tests/invalid/key-start-bracket.toml
similarity index 100%
rename from tests/fixtures/invalid/key-start-bracket.toml
rename to crates/test-suite/tests/invalid/key-start-bracket.toml
diff --git a/tests/fixtures/invalid/key-two-equals.toml b/crates/test-suite/tests/invalid/key-two-equals.toml
similarity index 100%
rename from tests/fixtures/invalid/key-two-equals.toml
rename to crates/test-suite/tests/invalid/key-two-equals.toml
diff --git a/tests/fixtures/invalid/string-bad-byte-escape.toml b/crates/test-suite/tests/invalid/string-bad-byte-escape.toml
similarity index 100%
rename from tests/fixtures/invalid/string-bad-byte-escape.toml
rename to crates/test-suite/tests/invalid/string-bad-byte-escape.toml
diff --git a/tests/fixtures/invalid/string-bad-escape.toml b/crates/test-suite/tests/invalid/string-bad-escape.toml
similarity index 100%
rename from tests/fixtures/invalid/string-bad-escape.toml
rename to crates/test-suite/tests/invalid/string-bad-escape.toml
diff --git a/crates/test-suite/tests/invalid/string-bad-line-ending-escape.toml b/crates/test-suite/tests/invalid/string-bad-line-ending-escape.toml
new file mode 100644
index 00000000..32e2c486
--- /dev/null
+++ b/crates/test-suite/tests/invalid/string-bad-line-ending-escape.toml
@@ -0,0 +1,3 @@
+invalid-escape = """\
+    This string has a non whitespace-character after the line ending escape. \  a
+"""
diff --git a/tests/fixtures/invalid/string-byte-escapes.toml b/crates/test-suite/tests/invalid/string-byte-escapes.toml
similarity index 100%
rename from tests/fixtures/invalid/string-byte-escapes.toml
rename to crates/test-suite/tests/invalid/string-byte-escapes.toml
diff --git a/tests/fixtures/invalid/string-no-close.toml b/crates/test-suite/tests/invalid/string-no-close.toml
similarity index 100%
rename from tests/fixtures/invalid/string-no-close.toml
rename to crates/test-suite/tests/invalid/string-no-close.toml
diff --git a/tests/fixtures/invalid/table-array-implicit.toml b/crates/test-suite/tests/invalid/table-array-implicit.toml
similarity index 100%
rename from tests/fixtures/invalid/table-array-implicit.toml
rename to crates/test-suite/tests/invalid/table-array-implicit.toml
diff --git a/tests/fixtures/invalid/table-array-malformed-bracket.toml b/crates/test-suite/tests/invalid/table-array-malformed-bracket.toml
similarity index 100%
rename from tests/fixtures/invalid/table-array-malformed-bracket.toml
rename to crates/test-suite/tests/invalid/table-array-malformed-bracket.toml
diff --git a/tests/fixtures/invalid/table-array-malformed-empty.toml b/crates/test-suite/tests/invalid/table-array-malformed-empty.toml
similarity index 100%
rename from tests/fixtures/invalid/table-array-malformed-empty.toml
rename to crates/test-suite/tests/invalid/table-array-malformed-empty.toml
diff --git a/tests/fixtures/invalid/table-empty.toml b/crates/test-suite/tests/invalid/table-empty.toml
similarity index 100%
rename from tests/fixtures/invalid/table-empty.toml
rename to crates/test-suite/tests/invalid/table-empty.toml
diff --git a/tests/fixtures/invalid/table-nested-brackets-close.toml b/crates/test-suite/tests/invalid/table-nested-brackets-close.toml
similarity index 100%
rename from tests/fixtures/invalid/table-nested-brackets-close.toml
rename to crates/test-suite/tests/invalid/table-nested-brackets-close.toml
diff --git a/tests/fixtures/invalid/table-nested-brackets-open.toml b/crates/test-suite/tests/invalid/table-nested-brackets-open.toml
similarity index 100%
rename from tests/fixtures/invalid/table-nested-brackets-open.toml
rename to crates/test-suite/tests/invalid/table-nested-brackets-open.toml
diff --git a/tests/fixtures/invalid/table-whitespace.toml b/crates/test-suite/tests/invalid/table-whitespace.toml
similarity index 100%
rename from tests/fixtures/invalid/table-whitespace.toml
rename to crates/test-suite/tests/invalid/table-whitespace.toml
diff --git a/tests/fixtures/invalid/table-with-pound.toml b/crates/test-suite/tests/invalid/table-with-pound.toml
similarity index 100%
rename from tests/fixtures/invalid/table-with-pound.toml
rename to crates/test-suite/tests/invalid/table-with-pound.toml
diff --git a/tests/fixtures/invalid/text-after-array-entries.toml b/crates/test-suite/tests/invalid/text-after-array-entries.toml
similarity index 100%
rename from tests/fixtures/invalid/text-after-array-entries.toml
rename to crates/test-suite/tests/invalid/text-after-array-entries.toml
diff --git a/tests/fixtures/invalid/text-after-integer.toml b/crates/test-suite/tests/invalid/text-after-integer.toml
similarity index 100%
rename from tests/fixtures/invalid/text-after-integer.toml
rename to crates/test-suite/tests/invalid/text-after-integer.toml
diff --git a/tests/fixtures/invalid/text-after-string.toml b/crates/test-suite/tests/invalid/text-after-string.toml
similarity index 100%
rename from tests/fixtures/invalid/text-after-string.toml
rename to crates/test-suite/tests/invalid/text-after-string.toml
diff --git a/tests/fixtures/invalid/text-after-table.toml b/crates/test-suite/tests/invalid/text-after-table.toml
similarity index 100%
rename from tests/fixtures/invalid/text-after-table.toml
rename to crates/test-suite/tests/invalid/text-after-table.toml
diff --git a/tests/fixtures/invalid/text-before-array-separator.toml b/crates/test-suite/tests/invalid/text-before-array-separator.toml
similarity index 100%
rename from tests/fixtures/invalid/text-before-array-separator.toml
rename to crates/test-suite/tests/invalid/text-before-array-separator.toml
diff --git a/tests/fixtures/invalid/text-in-array.toml b/crates/test-suite/tests/invalid/text-in-array.toml
similarity index 100%
rename from tests/fixtures/invalid/text-in-array.toml
rename to crates/test-suite/tests/invalid/text-in-array.toml
diff --git a/crates/test-suite/tests/macros.rs b/crates/test-suite/tests/macros.rs
new file mode 100644
index 00000000..20831a05
--- /dev/null
+++ b/crates/test-suite/tests/macros.rs
@@ -0,0 +1,368 @@
+#![recursion_limit = "256"]
+
+#[macro_use]
+extern crate toml;
+
+use std::f64;
+
+macro_rules! table {
+    ($($key:expr => $value:expr,)*) => {{
+        // https://github.com/rust-lang/rust/issues/60643
+        #[allow(unused_mut)]
+        let mut table = toml::value::Table::new();
+        $(
+            table.insert($key.to_string(), $value.into());
+        )*
+        toml::Value::Table(table)
+    }};
+}
+
+macro_rules! array {
+    ($($element:expr,)*) => {{
+        // https://github.com/rust-lang/rust/issues/60643
+        #[allow(unused_mut)]
+        let mut array = toml::value::Array::new();
+        $(
+            array.push($element.into());
+        )*
+        toml::Value::Array(array)
+    }};
+}
+
+macro_rules! datetime {
+    ($s:tt) => {
+        $s.parse::<toml::value::Datetime>().unwrap()
+    };
+}
+
+#[test]
+fn test_cargo_toml() {
+    // Simple sanity check of:
+    //
+    //   - Ordinary tables
+    //   - Inline tables
+    //   - Inline arrays
+    //   - String values
+    //   - Table keys containing hyphen
+    //   - Table headers containing hyphen
+    let actual = toml! {
+        [package]
+        name = "toml"
+        version = "0.4.5"
+        authors = ["Alex Crichton <alex@alexcrichton.com>"]
+
+        [badges]
+        travis-ci = { repository = "alexcrichton/toml-rs" }
+
+        [dependencies]
+        serde = "1.0"
+
+        [dev-dependencies]
+        serde_derive = "1.0"
+        serde_json = "1.0"
+    };
+
+    let expected = table! {
+        "package" => table! {
+            "name" => "toml".to_owned(),
+            "version" => "0.4.5".to_owned(),
+            "authors" => array! {
+                "Alex Crichton <alex@alexcrichton.com>".to_owned(),
+            },
+        },
+        "badges" => table! {
+            "travis-ci" => table! {
+                "repository" => "alexcrichton/toml-rs".to_owned(),
+            },
+        },
+        "dependencies" => table! {
+            "serde" => "1.0".to_owned(),
+        },
+        "dev-dependencies" => table! {
+            "serde_derive" => "1.0".to_owned(),
+            "serde_json" => "1.0".to_owned(),
+        },
+    };
+
+    assert_eq!(actual, expected);
+}
+
+#[test]
+fn test_array() {
+    // Copied from the TOML spec.
+    let actual = toml! {
+        [[fruit]]
+        name = "apple"
+
+        [fruit.physical]
+        color = "red"
+        shape = "round"
+
+        [[fruit.variety]]
+        name = "red delicious"
+
+        [[fruit.variety]]
+        name = "granny smith"
+
+        [[fruit]]
+        name = "banana"
+
+        [[fruit.variety]]
+        name = "plantain"
+    };
+
+    let expected = table! {
+        "fruit" => array! {
+            table! {
+                "name" => "apple",
+                "physical" => table! {
+                    "color" => "red",
+                    "shape" => "round",
+                },
+                "variety" => array! {
+                    table! {
+                        "name" => "red delicious",
+                    },
+                    table! {
+                        "name" => "granny smith",
+                    },
+                },
+            },
+            table! {
+                "name" => "banana",
+                "variety" => array! {
+                    table! {
+                        "name" => "plantain",
+                    },
+                },
+            },
+        },
+    };
+
+    assert_eq!(actual, expected);
+}
+
+#[test]
+fn test_number() {
+    let actual = toml! {
+        positive = 1
+        negative = -1
+        table = { positive = 1, negative = -1 }
+        array = [ 1, -1 ]
+        neg_zero = -0
+        pos_zero = +0
+        float = 3.14
+
+        sf1 = inf
+        sf2 = +inf
+        sf3 = -inf
+        sf7 = +0.0
+        sf8 = -0.0
+
+        hex = 0xa_b_c
+        oct = 0o755
+        bin = 0b11010110
+    };
+
+    let expected = table! {
+        "positive" => 1,
+        "negative" => -1,
+        "table" => table! {
+            "positive" => 1,
+            "negative" => -1,
+        },
+        "array" => array! {
+            1,
+            -1,
+        },
+        "neg_zero" => -0,
+        "pos_zero" => 0,
+        "float" => 3.14,
+        "sf1" => f64::INFINITY,
+        "sf2" => f64::INFINITY,
+        "sf3" => f64::NEG_INFINITY,
+        "sf7" => 0.0,
+        "sf8" => -0.0,
+        "hex" => 2748,
+        "oct" => 493,
+        "bin" => 214,
+    };
+
+    assert_eq!(actual, expected);
+}
+
+#[test]
+fn test_nan() {
+    let actual = toml! {
+        sf4 = nan
+        sf5 = +nan
+        sf6 = -nan
+    };
+    assert!(actual["sf4"].as_float().unwrap().is_nan());
+    assert!(actual["sf5"].as_float().unwrap().is_nan());
+    assert!(actual["sf6"].as_float().unwrap().is_nan());
+}
+
+#[test]
+fn test_datetime() {
+    let actual = toml! {
+        // Copied from the TOML spec.
+        odt1 = 1979-05-27T07:32:00Z
+        odt2 = 1979-05-27T00:32:00-07:00
+        odt3 = 1979-05-27T00:32:00.999999-07:00
+        odt4 = 1979-05-27 07:32:00Z
+        ldt1 = 1979-05-27T07:32:00
+        ldt2 = 1979-05-27T00:32:00.999999
+        ld1 = 1979-05-27
+        lt1 = 07:32:00
+        lt2 = 00:32:00.999999
+
+        table = {
+            odt1 = 1979-05-27T07:32:00Z,
+            odt2 = 1979-05-27T00:32:00-07:00,
+            odt3 = 1979-05-27T00:32:00.999999-07:00,
+            odt4 = 1979-05-27 07:32:00Z,
+            ldt1 = 1979-05-27T07:32:00,
+            ldt2 = 1979-05-27T00:32:00.999999,
+            ld1 = 1979-05-27,
+            lt1 = 07:32:00,
+            lt2 = 00:32:00.999999,
+        }
+
+        array = [
+            1979-05-27T07:32:00Z,
+            1979-05-27T00:32:00-07:00,
+            1979-05-27T00:32:00.999999-07:00,
+            1979-05-27 07:32:00Z,
+            1979-05-27T07:32:00,
+            1979-05-27T00:32:00.999999,
+            1979-05-27,
+            07:32:00,
+            00:32:00.999999,
+        ]
+    };
+
+    let expected = table! {
+        "odt1" => datetime!("1979-05-27T07:32:00Z"),
+        "odt2" => datetime!("1979-05-27T00:32:00-07:00"),
+        "odt3" => datetime!("1979-05-27T00:32:00.999999-07:00"),
+        "odt4" => datetime!("1979-05-27 07:32:00Z"),
+        "ldt1" => datetime!("1979-05-27T07:32:00"),
+        "ldt2" => datetime!("1979-05-27T00:32:00.999999"),
+        "ld1" => datetime!("1979-05-27"),
+        "lt1" => datetime!("07:32:00"),
+        "lt2" => datetime!("00:32:00.999999"),
+
+        "table" => table! {
+            "odt1" => datetime!("1979-05-27T07:32:00Z"),
+            "odt2" => datetime!("1979-05-27T00:32:00-07:00"),
+            "odt3" => datetime!("1979-05-27T00:32:00.999999-07:00"),
+            "odt4" => datetime!("1979-05-27 07:32:00Z"),
+            "ldt1" => datetime!("1979-05-27T07:32:00"),
+            "ldt2" => datetime!("1979-05-27T00:32:00.999999"),
+            "ld1" => datetime!("1979-05-27"),
+            "lt1" => datetime!("07:32:00"),
+            "lt2" => datetime!("00:32:00.999999"),
+        },
+
+        "array" => array! {
+            datetime!("1979-05-27T07:32:00Z"),
+            datetime!("1979-05-27T00:32:00-07:00"),
+            datetime!("1979-05-27T00:32:00.999999-07:00"),
+            datetime!("1979-05-27 07:32:00Z"),
+            datetime!("1979-05-27T07:32:00"),
+            datetime!("1979-05-27T00:32:00.999999"),
+            datetime!("1979-05-27"),
+            datetime!("07:32:00"),
+            datetime!("00:32:00.999999"),
+        },
+    };
+
+    assert_eq!(actual, expected);
+}
+
+// This test requires rustc >= 1.20.
+#[test]
+fn test_quoted_key() {
+    let actual = toml! {
+        "quoted" = true
+        table = { "quoted" = true }
+
+        [target."cfg(windows)".dependencies]
+        winapi = "0.2.8"
+    };
+
+    let expected = table! {
+        "quoted" => true,
+        "table" => table! {
+            "quoted" => true,
+        },
+        "target" => table! {
+            "cfg(windows)" => table! {
+                "dependencies" => table! {
+                    "winapi" => "0.2.8",
+                },
+            },
+        },
+    };
+
+    assert_eq!(actual, expected);
+}
+
+#[test]
+fn test_empty() {
+    let actual = toml! {
+        empty_inline_table = {}
+        empty_inline_array = []
+
+        [empty_table]
+
+        [[empty_array]]
+    };
+
+    let expected = table! {
+        "empty_inline_table" => table! {},
+        "empty_inline_array" => array! {},
+        "empty_table" => table! {},
+        "empty_array" => array! {
+            table! {},
+        },
+    };
+
+    assert_eq!(actual, expected);
+}
+
+#[test]
+fn test_dotted_keys() {
+    let actual = toml! {
+        a.b = 123
+        a.c = 1979-05-27T07:32:00Z
+        [table]
+        a.b.c = 1
+        a  .  b  .  d = 2
+        in = { type.name = "cat", type.color = "blue" }
+    };
+
+    let expected = table! {
+        "a" => table! {
+            "b" => 123,
+            "c" => datetime!("1979-05-27T07:32:00Z"),
+        },
+        "table" => table! {
+            "a" => table! {
+                "b" => table! {
+                    "c" => 1,
+                    "d" => 2,
+                },
+            },
+            "in" => table! {
+                "type" => table! {
+                    "name" => "cat",
+                    "color" => "blue",
+                },
+            },
+        },
+    };
+
+    assert_eq!(actual, expected);
+}
diff --git a/crates/test-suite/tests/parser.rs b/crates/test-suite/tests/parser.rs
new file mode 100644
index 00000000..b28fce04
--- /dev/null
+++ b/crates/test-suite/tests/parser.rs
@@ -0,0 +1,713 @@
+extern crate toml;
+
+use toml::Value;
+
+macro_rules! bad {
+    ($toml:expr, $msg:expr) => {
+        match $toml.parse::<toml::Value>() {
+            Ok(s) => panic!("parsed to: {:#?}", s),
+            Err(e) => assert_eq!(e.to_string(), $msg),
+        }
+    };
+}
+
+#[test]
+fn crlf() {
+    "\
+     [project]\r\n\
+     \r\n\
+     name = \"splay\"\r\n\
+     version = \"0.1.0\"\r\n\
+     authors = [\"alex@crichton.co\"]\r\n\
+     \r\n\
+     [[lib]]\r\n\
+     \r\n\
+     path = \"lib.rs\"\r\n\
+     name = \"splay\"\r\n\
+     description = \"\"\"\
+     A Rust implementation of a TAR file reader and writer. This library does not\r\n\
+     currently handle compression, but it is abstract over all I/O readers and\r\n\
+     writers. Additionally, great lengths are taken to ensure that the entire\r\n\
+     contents are never required to be entirely resident in memory all at once.\r\n\
+     \"\"\"\
+     "
+    .parse::<Value>()
+    .unwrap();
+}
+
+#[test]
+fn fun_with_strings() {
+    let table = r#"
+bar = "\U00000000"
+key1 = "One\nTwo"
+key2 = """One\nTwo"""
+key3 = """
+One
+Two"""
+
+key4 = "The quick brown fox jumps over the lazy dog."
+key5 = """
+The quick brown \
+
+
+fox jumps over \
+the lazy dog."""
+key6 = """\
+   The quick brown \
+   fox jumps over \
+   the lazy dog.\
+   """
+# What you see is what you get.
+winpath  = 'C:\Users\nodejs\templates'
+winpath2 = '\\ServerX\admin$\system32\'
+quoted   = 'Tom "Dubs" Preston-Werner'
+regex    = '<\i\c*\s*>'
+
+regex2 = '''I [dw]on't need \d{2} apples'''
+lines  = '''
+The first newline is
+trimmed in raw strings.
+All other whitespace
+is preserved.
+'''
+"#
+    .parse::<Value>()
+    .unwrap();
+    assert_eq!(table["bar"].as_str(), Some("\0"));
+    assert_eq!(table["key1"].as_str(), Some("One\nTwo"));
+    assert_eq!(table["key2"].as_str(), Some("One\nTwo"));
+    assert_eq!(table["key3"].as_str(), Some("One\nTwo"));
+
+    let msg = "The quick brown fox jumps over the lazy dog.";
+    assert_eq!(table["key4"].as_str(), Some(msg));
+    assert_eq!(table["key5"].as_str(), Some(msg));
+    assert_eq!(table["key6"].as_str(), Some(msg));
+
+    assert_eq!(
+        table["winpath"].as_str(),
+        Some(r"C:\Users\nodejs\templates")
+    );
+    assert_eq!(
+        table["winpath2"].as_str(),
+        Some(r"\\ServerX\admin$\system32\")
+    );
+    assert_eq!(
+        table["quoted"].as_str(),
+        Some(r#"Tom "Dubs" Preston-Werner"#)
+    );
+    assert_eq!(table["regex"].as_str(), Some(r"<\i\c*\s*>"));
+    assert_eq!(
+        table["regex2"].as_str(),
+        Some(r"I [dw]on't need \d{2} apples")
+    );
+    assert_eq!(
+        table["lines"].as_str(),
+        Some(
+            "The first newline is\n\
+             trimmed in raw strings.\n\
+             All other whitespace\n\
+             is preserved.\n"
+        )
+    );
+}
+
+#[test]
+fn tables_in_arrays() {
+    let table = r#"
+[[foo]]
+#…
+[foo.bar]
+#…
+
+[[foo]] # ...
+#…
+[foo.bar]
+#...
+"#
+    .parse::<Value>()
+    .unwrap();
+    table["foo"][0]["bar"].as_table().unwrap();
+    table["foo"][1]["bar"].as_table().unwrap();
+}
+
+#[test]
+fn empty_table() {
+    let table = r#"
+[foo]"#
+        .parse::<Value>()
+        .unwrap();
+    table["foo"].as_table().unwrap();
+}
+
+#[test]
+fn fruit() {
+    let table = r#"
+[[fruit]]
+name = "apple"
+
+[fruit.physical]
+color = "red"
+shape = "round"
+
+[[fruit.variety]]
+name = "red delicious"
+
+[[fruit.variety]]
+name = "granny smith"
+
+[[fruit]]
+name = "banana"
+
+[[fruit.variety]]
+name = "plantain"
+"#
+    .parse::<Value>()
+    .unwrap();
+    assert_eq!(table["fruit"][0]["name"].as_str(), Some("apple"));
+    assert_eq!(table["fruit"][0]["physical"]["color"].as_str(), Some("red"));
+    assert_eq!(
+        table["fruit"][0]["physical"]["shape"].as_str(),
+        Some("round")
+    );
+    assert_eq!(
+        table["fruit"][0]["variety"][0]["name"].as_str(),
+        Some("red delicious")
+    );
+    assert_eq!(
+        table["fruit"][0]["variety"][1]["name"].as_str(),
+        Some("granny smith")
+    );
+    assert_eq!(table["fruit"][1]["name"].as_str(), Some("banana"));
+    assert_eq!(
+        table["fruit"][1]["variety"][0]["name"].as_str(),
+        Some("plantain")
+    );
+}
+
+#[test]
+fn stray_cr() {
+    bad!("\r", "unexpected character found: `\\r` at line 1 column 1");
+    bad!(
+        "a = [ \r ]",
+        "unexpected character found: `\\r` at line 1 column 7"
+    );
+    bad!(
+        "a = \"\"\"\r\"\"\"",
+        "invalid character in string: `\\r` at line 1 column 8"
+    );
+    bad!(
+        "a = \"\"\"\\  \r  \"\"\"",
+        "invalid escape character in string: ` ` at line 1 column 9"
+    );
+    bad!(
+        "a = '''\r'''",
+        "invalid character in string: `\\r` at line 1 column 8"
+    );
+    bad!(
+        "a = '\r'",
+        "invalid character in string: `\\r` at line 1 column 6"
+    );
+    bad!(
+        "a = \"\r\"",
+        "invalid character in string: `\\r` at line 1 column 6"
+    );
+}
+
+#[test]
+fn blank_literal_string() {
+    let table = "foo = ''".parse::<Value>().unwrap();
+    assert_eq!(table["foo"].as_str(), Some(""));
+}
+
+#[test]
+fn many_blank() {
+    let table = "foo = \"\"\"\n\n\n\"\"\"".parse::<Value>().unwrap();
+    assert_eq!(table["foo"].as_str(), Some("\n\n"));
+}
+
+#[test]
+fn literal_eats_crlf() {
+    let table = "
+        foo = \"\"\"\\\r\n\"\"\"
+        bar = \"\"\"\\\r\n   \r\n   \r\n   a\"\"\"
+    "
+    .parse::<Value>()
+    .unwrap();
+    assert_eq!(table["foo"].as_str(), Some(""));
+    assert_eq!(table["bar"].as_str(), Some("a"));
+}
+
+#[test]
+fn string_no_newline() {
+    bad!("a = \"\n\"", "newline in string found at line 1 column 6");
+    bad!("a = '\n'", "newline in string found at line 1 column 6");
+}
+
+#[test]
+fn bad_leading_zeros() {
+    bad!("a = 00", "invalid number at line 1 column 6");
+    bad!("a = -00", "invalid number at line 1 column 7");
+    bad!("a = +00", "invalid number at line 1 column 7");
+    bad!("a = 00.0", "invalid number at line 1 column 6");
+    bad!("a = -00.0", "invalid number at line 1 column 7");
+    bad!("a = +00.0", "invalid number at line 1 column 7");
+    bad!(
+        "a = 9223372036854775808",
+        "invalid number at line 1 column 5"
+    );
+    bad!(
+        "a = -9223372036854775809",
+        "invalid number at line 1 column 5"
+    );
+}
+
+#[test]
+fn bad_floats() {
+    bad!("a = 0.", "invalid number at line 1 column 7");
+    bad!("a = 0.e", "invalid number at line 1 column 7");
+    bad!("a = 0.E", "invalid number at line 1 column 7");
+    bad!("a = 0.0E", "invalid number at line 1 column 5");
+    bad!("a = 0.0e", "invalid number at line 1 column 5");
+    bad!("a = 0.0e-", "invalid number at line 1 column 9");
+    bad!("a = 0.0e+", "invalid number at line 1 column 5");
+}
+
+#[test]
+fn floats() {
+    macro_rules! t {
+        ($actual:expr, $expected:expr) => {{
+            let f = format!("foo = {}", $actual);
+            println!("{}", f);
+            let a = f.parse::<Value>().unwrap();
+            assert_eq!(a["foo"].as_float().unwrap(), $expected);
+        }};
+    }
+
+    t!("1.0", 1.0);
+    t!("1.0e0", 1.0);
+    t!("1.0e+0", 1.0);
+    t!("1.0e-0", 1.0);
+    t!("1E-0", 1.0);
+    t!("1.001e-0", 1.001);
+    t!("2e10", 2e10);
+    t!("2e+10", 2e10);
+    t!("2e-10", 2e-10);
+    t!("2_0.0", 20.0);
+    t!("2_0.0_0e1_0", 20.0e10);
+    t!("2_0.1_0e1_0", 20.1e10);
+}
+
+#[test]
+fn bare_key_names() {
+    let a = "
+        foo = 3
+        foo_3 = 3
+        foo_-2--3--r23f--4-f2-4 = 3
+        _ = 3
+        - = 3
+        8 = 8
+        \"a\" = 3
+        \"!\" = 3
+        \"a^b\" = 3
+        \"\\\"\" = 3
+        \"character encoding\" = \"value\"
+        'ʎǝʞ' = \"value\"
+    "
+    .parse::<Value>()
+    .unwrap();
+    &a["foo"];
+    &a["-"];
+    &a["_"];
+    &a["8"];
+    &a["foo_3"];
+    &a["foo_-2--3--r23f--4-f2-4"];
+    &a["a"];
+    &a["!"];
+    &a["\""];
+    &a["character encoding"];
+    &a["ʎǝʞ"];
+}
+
+#[test]
+fn bad_keys() {
+    bad!(
+        "key\n=3",
+        "expected an equals, found a newline at line 1 column 4"
+    );
+    bad!(
+        "key=\n3",
+        "expected a value, found a newline at line 1 column 5"
+    );
+    bad!(
+        "key|=3",
+        "unexpected character found: `|` at line 1 column 4"
+    );
+    bad!(
+        "=3",
+        "expected a table key, found an equals at line 1 column 1"
+    );
+    bad!(
+        "\"\"|=3",
+        "unexpected character found: `|` at line 1 column 3"
+    );
+    bad!("\"\n\"|=3", "newline in string found at line 1 column 2");
+    bad!(
+        "\"\r\"|=3",
+        "invalid character in string: `\\r` at line 1 column 2"
+    );
+    bad!(
+        "''''''=3",
+        "multiline strings are not allowed for key at line 1 column 1"
+    );
+    bad!(
+        "\"\"\"\"\"\"=3",
+        "multiline strings are not allowed for key at line 1 column 1"
+    );
+    bad!(
+        "'''key'''=3",
+        "multiline strings are not allowed for key at line 1 column 1"
+    );
+    bad!(
+        "\"\"\"key\"\"\"=3",
+        "multiline strings are not allowed for key at line 1 column 1"
+    );
+}
+
+#[test]
+fn bad_table_names() {
+    bad!(
+        "[]",
+        "expected a table key, found a right bracket at line 1 column 2"
+    );
+    bad!(
+        "[.]",
+        "expected a table key, found a period at line 1 column 2"
+    );
+    bad!(
+        "[a.]",
+        "expected a table key, found a right bracket at line 1 column 4"
+    );
+    bad!("[!]", "unexpected character found: `!` at line 1 column 2");
+    bad!("[\"\n\"]", "newline in string found at line 1 column 3");
+    bad!(
+        "[a.b]\n[a.\"b\"]",
+        "redefinition of table `a.b` for key `a.b` at line 2 column 1"
+    );
+    bad!("[']", "unterminated string at line 1 column 2");
+    bad!("[''']", "unterminated string at line 1 column 2");
+    bad!(
+        "['''''']",
+        "multiline strings are not allowed for key at line 1 column 2"
+    );
+    bad!(
+        "['''foo''']",
+        "multiline strings are not allowed for key at line 1 column 2"
+    );
+    bad!(
+        "[\"\"\"bar\"\"\"]",
+        "multiline strings are not allowed for key at line 1 column 2"
+    );
+    bad!("['\n']", "newline in string found at line 1 column 3");
+    bad!("['\r\n']", "newline in string found at line 1 column 3");
+}
+
+#[test]
+fn table_names() {
+    let a = "
+        [a.\"b\"]
+        [\"f f\"]
+        [\"f.f\"]
+        [\"\\\"\"]
+        ['a.a']
+        ['\"\"']
+    "
+    .parse::<Value>()
+    .unwrap();
+    println!("{:?}", a);
+    &a["a"]["b"];
+    &a["f f"];
+    &a["f.f"];
+    &a["\""];
+    &a["\"\""];
+}
+
+#[test]
+fn invalid_bare_numeral() {
+    bad!("4", "expected an equals, found eof at line 1 column 2");
+}
+
+#[test]
+fn inline_tables() {
+    "a = {}".parse::<Value>().unwrap();
+    "a = {b=1}".parse::<Value>().unwrap();
+    "a = {   b   =   1    }".parse::<Value>().unwrap();
+    "a = {a=1,b=2}".parse::<Value>().unwrap();
+    "a = {a=1,b=2,c={}}".parse::<Value>().unwrap();
+
+    bad!(
+        "a = {a=1,}",
+        "expected a table key, found a right brace at line 1 column 10"
+    );
+    bad!(
+        "a = {,}",
+        "expected a table key, found a comma at line 1 column 6"
+    );
+    bad!(
+        "a = {a=1,a=1}",
+        "duplicate key: `a` for key `a` at line 1 column 5"
+    );
+    bad!(
+        "a = {\n}",
+        "expected a table key, found a newline at line 1 column 6"
+    );
+    bad!(
+        "a = {",
+        "expected a table key, found eof at line 1 column 6"
+    );
+
+    "a = {a=[\n]}".parse::<Value>().unwrap();
+    "a = {\"a\"=[\n]}".parse::<Value>().unwrap();
+    "a = [\n{},\n{},\n]".parse::<Value>().unwrap();
+}
+
+#[test]
+fn number_underscores() {
+    macro_rules! t {
+        ($actual:expr, $expected:expr) => {{
+            let f = format!("foo = {}", $actual);
+            let table = f.parse::<Value>().unwrap();
+            assert_eq!(table["foo"].as_integer().unwrap(), $expected);
+        }};
+    }
+
+    t!("1_0", 10);
+    t!("1_0_0", 100);
+    t!("1_000", 1000);
+    t!("+1_000", 1000);
+    t!("-1_000", -1000);
+}
+
+#[test]
+fn bad_underscores() {
+    bad!("foo = 0_", "invalid number at line 1 column 7");
+    bad!("foo = 0__0", "invalid number at line 1 column 7");
+    bad!(
+        "foo = __0",
+        "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
+    );
+    bad!("foo = 1_0_", "invalid number at line 1 column 7");
+}
+
+#[test]
+fn bad_unicode_codepoint() {
+    bad!(
+        "foo = \"\\uD800\"",
+        "invalid escape value: `55296` at line 1 column 9"
+    );
+}
+
+#[test]
+fn bad_strings() {
+    bad!(
+        "foo = \"\\uxx\"",
+        "invalid hex escape character in string: `x` at line 1 column 10"
+    );
+    bad!(
+        "foo = \"\\u\"",
+        "invalid hex escape character in string: `\\\"` at line 1 column 10"
+    );
+    bad!("foo = \"\\", "unterminated string at line 1 column 7");
+    bad!("foo = '", "unterminated string at line 1 column 7");
+}
+
+#[test]
+fn empty_string() {
+    assert_eq!(
+        "foo = \"\"".parse::<Value>().unwrap()["foo"]
+            .as_str()
+            .unwrap(),
+        ""
+    );
+}
+
+#[test]
+fn booleans() {
+    let table = "foo = true".parse::<Value>().unwrap();
+    assert_eq!(table["foo"].as_bool(), Some(true));
+
+    let table = "foo = false".parse::<Value>().unwrap();
+    assert_eq!(table["foo"].as_bool(), Some(false));
+
+    bad!(
+        "foo = true2",
+        "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
+    );
+    bad!(
+        "foo = false2",
+        "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
+    );
+    bad!(
+        "foo = t1",
+        "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
+    );
+    bad!(
+        "foo = f2",
+        "invalid TOML value, did you mean to use a quoted string? at line 1 column 7"
+    );
+}
+
+#[test]
+fn bad_nesting() {
+    bad!(
+        "
+        a = [2]
+        [[a]]
+        b = 5
+        ",
+        "duplicate key: `a` at line 3 column 9"
+    );
+    bad!(
+        "
+        a = 1
+        [a.b]
+        ",
+        "duplicate key: `a` at line 3 column 9"
+    );
+    bad!(
+        "
+        a = []
+        [a.b]
+        ",
+        "duplicate key: `a` at line 3 column 9"
+    );
+    bad!(
+        "
+        a = []
+        [[a.b]]
+        ",
+        "duplicate key: `a` at line 3 column 9"
+    );
+    bad!(
+        "
+        [a]
+        b = { c = 2, d = {} }
+        [a.b]
+        c = 2
+        ",
+        "duplicate key: `b` for key `a` at line 4 column 9"
+    );
+}
+
+#[test]
+fn bad_table_redefine() {
+    bad!(
+        "
+        [a]
+        foo=\"bar\"
+        [a.b]
+        foo=\"bar\"
+        [a]
+        ",
+        "redefinition of table `a` for key `a` at line 6 column 9"
+    );
+    bad!(
+        "
+        [a]
+        foo=\"bar\"
+        b = { foo = \"bar\" }
+        [a]
+        ",
+        "redefinition of table `a` for key `a` at line 5 column 9"
+    );
+    bad!(
+        "
+        [a]
+        b = {}
+        [a.b]
+        ",
+        "duplicate key: `b` for key `a` at line 4 column 9"
+    );
+
+    bad!(
+        "
+        [a]
+        b = {}
+        [a]
+        ",
+        "redefinition of table `a` for key `a` at line 4 column 9"
+    );
+}
+
+#[test]
+fn datetimes() {
+    macro_rules! t {
+        ($actual:expr) => {{
+            let f = format!("foo = {}", $actual);
+            let toml = f.parse::<Value>().expect(&format!("failed: {}", f));
+            assert_eq!(toml["foo"].as_datetime().unwrap().to_string(), $actual);
+        }};
+    }
+
+    t!("2016-09-09T09:09:09Z");
+    t!("2016-09-09T09:09:09.1Z");
+    t!("2016-09-09T09:09:09.2+10:00");
+    t!("2016-09-09T09:09:09.123456789-02:00");
+    bad!(
+        "foo = 2016-09-09T09:09:09.Z",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 2016-9-09T09:09:09Z",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 2016-09-09T09:09:09+2:00",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 2016-09-09T09:09:09-2:00",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+    bad!(
+        "foo = 2016-09-09T09:09:09Z-2:00",
+        "failed to parse datetime for key `foo` at line 1 column 7"
+    );
+}
+
+#[test]
+fn require_newline_after_value() {
+    bad!("0=0r=false", "invalid number at line 1 column 3");
+    bad!(
+        r#"
+0=""o=""m=""r=""00="0"q="""0"""e="""0"""
+"#,
+        "expected newline, found an identifier at line 2 column 5"
+    );
+    bad!(
+        r#"
+[[0000l0]]
+0="0"[[0000l0]]
+0="0"[[0000l0]]
+0="0"l="0"
+"#,
+        "expected newline, found a left bracket at line 3 column 6"
+    );
+    bad!(
+        r#"
+0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
+"#,
+        "expected newline, found an identifier at line 2 column 6"
+    );
+    bad!(
+        r#"
+0=0r0=0r=false
+"#,
+        "invalid number at line 2 column 3"
+    );
+    bad!(
+        r#"
+0=0r0=0r=falsefal=false
+"#,
+        "invalid number at line 2 column 3"
+    );
+}
diff --git a/crates/test-suite/tests/pretty.rs b/crates/test-suite/tests/pretty.rs
new file mode 100644
index 00000000..0c65e0f4
--- /dev/null
+++ b/crates/test-suite/tests/pretty.rs
@@ -0,0 +1,314 @@
+extern crate serde;
+extern crate toml;
+
+use serde::ser::Serialize;
+
+const NO_PRETTY: &'static str = "\
+[example]
+array = [\"item 1\", \"item 2\"]
+empty = []
+oneline = \"this has no newlines.\"
+text = \"\\nthis is the first line\\nthis is the second line\\n\"
+";
+
+#[test]
+fn no_pretty() {
+    let toml = NO_PRETTY;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    value
+        .serialize(&mut toml::Serializer::new(&mut result))
+        .unwrap();
+    println!("EXPECTED:\n{}", toml);
+    println!("\nRESULT:\n{}", result);
+    assert_eq!(toml, &result);
+}
+
+#[test]
+fn disable_pretty() {
+    let toml = NO_PRETTY;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    {
+        let mut serializer = toml::Serializer::pretty(&mut result);
+        serializer.pretty_string(false);
+        serializer.pretty_array(false);
+        value.serialize(&mut serializer).unwrap();
+    }
+    println!("EXPECTED:\n{}", toml);
+    println!("\nRESULT:\n{}", result);
+    assert_eq!(toml, &result);
+}
+
+const PRETTY_STD: &'static str = "\
+[example]
+array = [
+    'item 1',
+    'item 2',
+]
+empty = []
+one = ['one']
+oneline = 'this has no newlines.'
+text = '''
+this is the first line
+this is the second line
+'''
+";
+
+#[test]
+fn pretty_std() {
+    let toml = PRETTY_STD;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    value
+        .serialize(&mut toml::Serializer::pretty(&mut result))
+        .unwrap();
+    println!("EXPECTED:\n{}", toml);
+    println!("\nRESULT:\n{}", result);
+    assert_eq!(toml, &result);
+}
+
+const PRETTY_INDENT_2: &'static str = "\
+[example]
+array = [
+  'item 1',
+  'item 2',
+]
+empty = []
+one = ['one']
+oneline = 'this has no newlines.'
+text = '''
+this is the first line
+this is the second line
+'''
+three = [
+  'one',
+  'two',
+  'three',
+]
+";
+
+#[test]
+fn pretty_indent_2() {
+    let toml = PRETTY_INDENT_2;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    {
+        let mut serializer = toml::Serializer::pretty(&mut result);
+        serializer.pretty_array_indent(2);
+        value.serialize(&mut serializer).unwrap();
+    }
+    println!(">> Result:\n{}", result);
+    assert_eq!(toml, &result);
+}
+
+const PRETTY_INDENT_2_OTHER: &'static str = "\
+[example]
+array = [
+  \"item 1\",
+  \"item 2\",
+]
+empty = []
+oneline = \"this has no newlines.\"
+text = \"\\nthis is the first line\\nthis is the second line\\n\"
+";
+
+#[test]
+/// Test pretty indent when gotten the other way
+fn pretty_indent_2_other() {
+    let toml = PRETTY_INDENT_2_OTHER;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    {
+        let mut serializer = toml::Serializer::new(&mut result);
+        serializer.pretty_array_indent(2);
+        value.serialize(&mut serializer).unwrap();
+    }
+    assert_eq!(toml, &result);
+}
+
+const PRETTY_ARRAY_NO_COMMA: &'static str = "\
+[example]
+array = [
+    \"item 1\",
+    \"item 2\"
+]
+empty = []
+oneline = \"this has no newlines.\"
+text = \"\\nthis is the first line\\nthis is the second line\\n\"
+";
+#[test]
+/// Test pretty indent when gotten the other way
+fn pretty_indent_array_no_comma() {
+    let toml = PRETTY_ARRAY_NO_COMMA;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    {
+        let mut serializer = toml::Serializer::new(&mut result);
+        serializer.pretty_array_trailing_comma(false);
+        value.serialize(&mut serializer).unwrap();
+    }
+    assert_eq!(toml, &result);
+}
+
+const PRETTY_NO_STRING: &'static str = "\
+[example]
+array = [
+    \"item 1\",
+    \"item 2\",
+]
+empty = []
+oneline = \"this has no newlines.\"
+text = \"\\nthis is the first line\\nthis is the second line\\n\"
+";
+#[test]
+/// Test pretty indent when gotten the other way
+fn pretty_no_string() {
+    let toml = PRETTY_NO_STRING;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    {
+        let mut serializer = toml::Serializer::pretty(&mut result);
+        serializer.pretty_string(false);
+        value.serialize(&mut serializer).unwrap();
+    }
+    assert_eq!(toml, &result);
+}
+
+const PRETTY_TRICKY: &'static str = r##"[example]
+f = "\f"
+glass = '''
+Nothing too unusual, except that I can eat glass in:
+- Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. 
+- Polish: Mogę jeść szkło, i mi nie szkodzi. 
+- Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती. 
+- Japanese: 私はガラスを食べられます。それは私を傷つけません。 
+'''
+r = "\r"
+r_newline = """
+\r
+"""
+single = '''this is a single line but has '' cuz it's tricky'''
+single_tricky = "single line with ''' in it"
+tabs = '''
+this is pretty standard
+	except for some 	tabs right here
+'''
+text = """
+this is the first line.
+This has a ''' in it and \"\"\" cuz it's tricky yo
+Also ' and \" because why not
+this is the fourth line
+"""
+"##;
+
+#[test]
+fn pretty_tricky() {
+    let toml = PRETTY_TRICKY;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    value
+        .serialize(&mut toml::Serializer::pretty(&mut result))
+        .unwrap();
+    println!("EXPECTED:\n{}", toml);
+    println!("\nRESULT:\n{}", result);
+    assert_eq!(toml, &result);
+}
+
+const PRETTY_TABLE_ARRAY: &'static str = r##"[[array]]
+key = 'foo'
+
+[[array]]
+key = 'bar'
+
+[abc]
+doc = 'this is a table'
+
+[example]
+single = 'this is a single line string'
+"##;
+
+#[test]
+fn pretty_table_array() {
+    let toml = PRETTY_TABLE_ARRAY;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    value
+        .serialize(&mut toml::Serializer::pretty(&mut result))
+        .unwrap();
+    println!("EXPECTED:\n{}", toml);
+    println!("\nRESULT:\n{}", result);
+    assert_eq!(toml, &result);
+}
+
+const TABLE_ARRAY: &'static str = r##"[[array]]
+key = "foo"
+
+[[array]]
+key = "bar"
+
+[abc]
+doc = "this is a table"
+
+[example]
+single = "this is a single line string"
+"##;
+
+#[test]
+fn table_array() {
+    let toml = TABLE_ARRAY;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    value
+        .serialize(&mut toml::Serializer::new(&mut result))
+        .unwrap();
+    println!("EXPECTED:\n{}", toml);
+    println!("\nRESULT:\n{}", result);
+    assert_eq!(toml, &result);
+}
+
+const PRETTY_TRICKY_NON_LITERAL: &'static str = r##"[example]
+f = "\f"
+glass = """
+Nothing too unusual, except that I can eat glass in:
+- Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. 
+- Polish: Mogę jeść szkło, i mi nie szkodzi. 
+- Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती. 
+- Japanese: 私はガラスを食べられます。それは私を傷つけません。 
+"""
+plain = """
+This has a couple of lines
+Because it likes to.
+"""
+r = "\r"
+r_newline = """
+\r
+"""
+single = "this is a single line but has '' cuz it's tricky"
+single_tricky = "single line with ''' in it"
+tabs = """
+this is pretty standard
+\texcept for some \ttabs right here
+"""
+text = """
+this is the first line.
+This has a ''' in it and \"\"\" cuz it's tricky yo
+Also ' and \" because why not
+this is the fourth line
+"""
+"##;
+
+#[test]
+fn pretty_tricky_non_literal() {
+    let toml = PRETTY_TRICKY_NON_LITERAL;
+    let value: toml::Value = toml::from_str(toml).unwrap();
+    let mut result = String::with_capacity(128);
+    {
+        let mut serializer = toml::Serializer::pretty(&mut result);
+        serializer.pretty_string_literal(false);
+        value.serialize(&mut serializer).unwrap();
+    }
+    println!("EXPECTED:\n{}", toml);
+    println!("\nRESULT:\n{}", result);
+    assert_eq!(toml, &result);
+}
diff --git a/crates/test-suite/tests/serde.rs b/crates/test-suite/tests/serde.rs
new file mode 100644
index 00000000..56172bd3
--- /dev/null
+++ b/crates/test-suite/tests/serde.rs
@@ -0,0 +1,683 @@
+extern crate serde;
+extern crate serde_json;
+extern crate toml;
+#[macro_use]
+extern crate serde_derive;
+
+use serde::{Deserialize, Deserializer};
+use std::collections::{BTreeMap, HashSet};
+
+use toml::map::Map;
+use toml::Value;
+use toml::Value::{Array, Float, Integer, Table};
+
+macro_rules! t {
+    ($e:expr) => {
+        match $e {
+            Ok(t) => t,
+            Err(e) => panic!("{} failed with {}", stringify!($e), e),
+        }
+    };
+}
+
+macro_rules! equivalent {
+    ($literal:expr, $toml:expr,) => {{
+        let toml = $toml;
+        let literal = $literal;
+
+        // In/out of Value is equivalent
+        println!("try_from");
+        assert_eq!(t!(Value::try_from(literal.clone())), toml);
+        println!("try_into");
+        assert_eq!(literal, t!(toml.clone().try_into()));
+
+        // Through a string equivalent
+        println!("to_string(literal)");
+        assert_eq!(t!(toml::to_string(&literal)), toml.to_string());
+        println!("to_string(toml)");
+        assert_eq!(t!(toml::to_string(&toml)), toml.to_string());
+        println!("literal, from_str(toml)");
+        assert_eq!(literal, t!(toml::from_str(&toml.to_string())));
+        println!("toml, from_str(toml)");
+        assert_eq!(toml, t!(toml::from_str(&toml.to_string())));
+    }};
+}
+
+macro_rules! error {
+    ($ty:ty, $toml:expr, $msg_parse:expr, $msg_decode:expr) => {{
+        println!("attempting parsing");
+        match toml::from_str::<$ty>(&$toml.to_string()) {
+            Ok(_) => panic!("successful"),
+            Err(e) => assert_eq!(e.to_string(), $msg_parse),
+        }
+
+        println!("attempting toml decoding");
+        match $toml.try_into::<$ty>() {
+            Ok(_) => panic!("successful"),
+            Err(e) => assert_eq!(e.to_string(), $msg_decode),
+        }
+    }};
+}
+
+macro_rules! map( ($($k:ident: $v:expr),*) => ({
+    let mut _m = Map::new();
+    $(_m.insert(stringify!($k).to_string(), t!(Value::try_from($v)));)*
+    _m
+}) );
+
+#[test]
+fn smoke() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: isize,
+    }
+
+    equivalent!(Foo { a: 2 }, Table(map! { a: Integer(2) }),);
+}
+
+#[test]
+fn smoke_hyphen() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a_b: isize,
+    }
+
+    equivalent! {
+        Foo { a_b: 2 },
+        Table(map! { a_b: Integer(2) }),
+    }
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo2 {
+        #[serde(rename = "a-b")]
+        a_b: isize,
+    }
+
+    let mut m = Map::new();
+    m.insert("a-b".to_string(), Integer(2));
+    equivalent! {
+        Foo2 { a_b: 2 },
+        Table(m),
+    }
+}
+
+#[test]
+fn nested() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: isize,
+        b: Bar,
+    }
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Bar {
+        a: String,
+    }
+
+    equivalent! {
+        Foo { a: 2, b: Bar { a: "test".to_string() } },
+        Table(map! {
+            a: Integer(2),
+            b: Table(map! {
+                a: Value::String("test".to_string())
+            })
+        }),
+    }
+}
+
+#[test]
+fn application_decode_error() {
+    #[derive(PartialEq, Debug)]
+    struct Range10(usize);
+    impl<'de> Deserialize<'de> for Range10 {
+        fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Range10, D::Error> {
+            let x: usize = Deserialize::deserialize(d)?;
+            if x > 10 {
+                Err(serde::de::Error::custom("more than 10"))
+            } else {
+                Ok(Range10(x))
+            }
+        }
+    }
+    let d_good = Integer(5);
+    let d_bad1 = Value::String("not an isize".to_string());
+    let d_bad2 = Integer(11);
+
+    assert_eq!(Range10(5), d_good.try_into().unwrap());
+
+    let err1: Result<Range10, _> = d_bad1.try_into();
+    assert!(err1.is_err());
+    let err2: Result<Range10, _> = d_bad2.try_into();
+    assert!(err2.is_err());
+}
+
+#[test]
+fn array() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: Vec<isize>,
+    }
+
+    equivalent! {
+        Foo { a: vec![1, 2, 3, 4] },
+        Table(map! {
+            a: Array(vec![
+                Integer(1),
+                Integer(2),
+                Integer(3),
+                Integer(4)
+            ])
+        }),
+    };
+}
+
+#[test]
+fn inner_structs_with_options() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: Option<Box<Foo>>,
+        b: Bar,
+    }
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Bar {
+        a: String,
+        b: f64,
+    }
+
+    equivalent! {
+        Foo {
+            a: Some(Box::new(Foo {
+                a: None,
+                b: Bar { a: "foo".to_string(), b: 4.5 },
+            })),
+            b: Bar { a: "bar".to_string(), b: 1.0 },
+        },
+        Table(map! {
+            a: Table(map! {
+                b: Table(map! {
+                    a: Value::String("foo".to_string()),
+                    b: Float(4.5)
+                })
+            }),
+            b: Table(map! {
+                a: Value::String("bar".to_string()),
+                b: Float(1.0)
+            })
+        }),
+    }
+}
+
+#[test]
+fn hashmap() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        set: HashSet<char>,
+        map: BTreeMap<String, isize>,
+    }
+
+    equivalent! {
+        Foo {
+            map: {
+                let mut m = BTreeMap::new();
+                m.insert("foo".to_string(), 10);
+                m.insert("bar".to_string(), 4);
+                m
+            },
+            set: {
+                let mut s = HashSet::new();
+                s.insert('a');
+                s
+            },
+        },
+        Table(map! {
+            map: Table(map! {
+                foo: Integer(10),
+                bar: Integer(4)
+            }),
+            set: Array(vec![Value::String("a".to_string())])
+        }),
+    }
+}
+
+#[test]
+fn table_array() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: Vec<Bar>,
+    }
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Bar {
+        a: isize,
+    }
+
+    equivalent! {
+        Foo { a: vec![Bar { a: 1 }, Bar { a: 2 }] },
+        Table(map! {
+            a: Array(vec![
+                Table(map!{ a: Integer(1) }),
+                Table(map!{ a: Integer(2) }),
+            ])
+        }),
+    }
+}
+
+#[test]
+fn type_errors() {
+    #[derive(Deserialize)]
+    #[allow(dead_code)]
+    struct Foo {
+        bar: isize,
+    }
+
+    error! {
+        Foo,
+        Table(map! {
+            bar: Value::String("a".to_string())
+        }),
+        "invalid type: string \"a\", expected isize for key `bar` at line 1 column 7",
+        "invalid type: string \"a\", expected isize for key `bar`"
+    }
+
+    #[derive(Deserialize)]
+    #[allow(dead_code)]
+    struct Bar {
+        foo: Foo,
+    }
+
+    error! {
+        Bar,
+        Table(map! {
+            foo: Table(map! {
+                bar: Value::String("a".to_string())
+            })
+        }),
+        "invalid type: string \"a\", expected isize for key `foo.bar` at line 2 column 7",
+        "invalid type: string \"a\", expected isize for key `foo.bar`"
+    }
+}
+
+#[test]
+fn missing_errors() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug)]
+    struct Foo {
+        bar: isize,
+    }
+
+    error! {
+        Foo,
+        Table(map! { }),
+        "missing field `bar`",
+        "missing field `bar`"
+    }
+}
+
+#[test]
+fn parse_enum() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: E,
+    }
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    #[serde(untagged)]
+    enum E {
+        Bar(isize),
+        Baz(String),
+        Last(Foo2),
+    }
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo2 {
+        test: String,
+    }
+
+    equivalent! {
+        Foo { a: E::Bar(10) },
+        Table(map! { a: Integer(10) }),
+    }
+
+    equivalent! {
+        Foo { a: E::Baz("foo".to_string()) },
+        Table(map! { a: Value::String("foo".to_string()) }),
+    }
+
+    equivalent! {
+        Foo { a: E::Last(Foo2 { test: "test".to_string() }) },
+        Table(map! { a: Table(map! { test: Value::String("test".to_string()) }) }),
+    }
+}
+
+#[test]
+fn parse_enum_string() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: Sort,
+    }
+
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    #[serde(rename_all = "lowercase")]
+    enum Sort {
+        Asc,
+        Desc,
+    }
+
+    equivalent! {
+        Foo { a: Sort::Desc },
+        Table(map! { a: Value::String("desc".to_string()) }),
+    }
+}
+
+// #[test]
+// fn unused_fields() {
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Foo { a: isize }
+//
+//     let v = Foo { a: 2 };
+//     let mut d = Decoder::new(Table(map! {
+//         a, Integer(2),
+//         b, Integer(5)
+//     }));
+//     assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+//     assert_eq!(d.toml, Some(Table(map! {
+//         b, Integer(5)
+//     })));
+// }
+//
+// #[test]
+// fn unused_fields2() {
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Foo { a: Bar }
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Bar { a: isize }
+//
+//     let v = Foo { a: Bar { a: 2 } };
+//     let mut d = Decoder::new(Table(map! {
+//         a, Table(map! {
+//             a, Integer(2),
+//             b, Integer(5)
+//         })
+//     }));
+//     assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+//     assert_eq!(d.toml, Some(Table(map! {
+//         a, Table(map! {
+//             b, Integer(5)
+//         })
+//     })));
+// }
+//
+// #[test]
+// fn unused_fields3() {
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Foo { a: Bar }
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Bar { a: isize }
+//
+//     let v = Foo { a: Bar { a: 2 } };
+//     let mut d = Decoder::new(Table(map! {
+//         a, Table(map! {
+//             a, Integer(2)
+//         })
+//     }));
+//     assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+//     assert_eq!(d.toml, None);
+// }
+//
+// #[test]
+// fn unused_fields4() {
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Foo { a: BTreeMap<String, String> }
+//
+//     let v = Foo { a: map! { a, "foo".to_string() } };
+//     let mut d = Decoder::new(Table(map! {
+//         a, Table(map! {
+//             a, Value::String("foo".to_string())
+//         })
+//     }));
+//     assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+//     assert_eq!(d.toml, None);
+// }
+//
+// #[test]
+// fn unused_fields5() {
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Foo { a: Vec<String> }
+//
+//     let v = Foo { a: vec!["a".to_string()] };
+//     let mut d = Decoder::new(Table(map! {
+//         a, Array(vec![Value::String("a".to_string())])
+//     }));
+//     assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+//     assert_eq!(d.toml, None);
+// }
+//
+// #[test]
+// fn unused_fields6() {
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Foo { a: Option<Vec<String>> }
+//
+//     let v = Foo { a: Some(vec![]) };
+//     let mut d = Decoder::new(Table(map! {
+//         a, Array(vec![])
+//     }));
+//     assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+//     assert_eq!(d.toml, None);
+// }
+//
+// #[test]
+// fn unused_fields7() {
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Foo { a: Vec<Bar> }
+//     #[derive(Serialize, Deserialize, PartialEq, Debug)]
+//     struct Bar { a: isize }
+//
+//     let v = Foo { a: vec![Bar { a: 1 }] };
+//     let mut d = Decoder::new(Table(map! {
+//         a, Array(vec![Table(map! {
+//             a, Integer(1),
+//             b, Integer(2)
+//         })])
+//     }));
+//     assert_eq!(v, t!(Deserialize::deserialize(&mut d)));
+//
+//     assert_eq!(d.toml, Some(Table(map! {
+//         a, Array(vec![Table(map! {
+//             b, Integer(2)
+//         })])
+//     })));
+// }
+
+#[test]
+fn empty_arrays() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: Vec<Bar>,
+    }
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Bar;
+
+    equivalent! {
+        Foo { a: vec![] },
+        Table(map! {a: Array(Vec::new())}),
+    }
+}
+
+#[test]
+fn empty_arrays2() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Foo {
+        a: Option<Vec<Bar>>,
+    }
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Bar;
+
+    equivalent! {
+        Foo { a: None },
+        Table(map! {}),
+    }
+
+    equivalent! {
+        Foo { a: Some(vec![]) },
+        Table(map! { a: Array(vec![]) }),
+    }
+}
+
+#[test]
+fn extra_keys() {
+    #[derive(Serialize, Deserialize)]
+    struct Foo {
+        a: isize,
+    }
+
+    let toml = Table(map! { a: Integer(2), b: Integer(2) });
+    assert!(toml.clone().try_into::<Foo>().is_ok());
+    assert!(toml::from_str::<Foo>(&toml.to_string()).is_ok());
+}
+
+#[test]
+fn newtypes() {
+    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
+    struct A {
+        b: B,
+    }
+
+    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
+    struct B(u32);
+
+    equivalent! {
+        A { b: B(2) },
+        Table(map! { b: Integer(2) }),
+    }
+}
+
+#[test]
+fn newtypes2() {
+    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
+    struct A {
+        b: B,
+    }
+
+    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
+    struct B(Option<C>);
+
+    #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
+    struct C {
+        x: u32,
+        y: u32,
+        z: u32,
+    }
+
+    equivalent! {
+        A { b: B(Some(C { x: 0, y: 1, z: 2 })) },
+        Table(map! {
+            b: Table(map! {
+                x: Integer(0),
+                y: Integer(1),
+                z: Integer(2)
+            })
+        }),
+    }
+}
+
+#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
+struct CanBeEmpty {
+    a: Option<String>,
+    b: Option<String>,
+}
+
+#[test]
+fn table_structs_empty() {
+    let text = "[bar]\n\n[baz]\n\n[bazv]\na = \"foo\"\n\n[foo]\n";
+    let value: BTreeMap<String, CanBeEmpty> = toml::from_str(text).unwrap();
+    let mut expected: BTreeMap<String, CanBeEmpty> = BTreeMap::new();
+    expected.insert("bar".to_string(), CanBeEmpty::default());
+    expected.insert("baz".to_string(), CanBeEmpty::default());
+    expected.insert(
+        "bazv".to_string(),
+        CanBeEmpty {
+            a: Some("foo".to_string()),
+            b: None,
+        },
+    );
+    expected.insert("foo".to_string(), CanBeEmpty::default());
+    assert_eq!(value, expected);
+    assert_eq!(toml::to_string(&value).unwrap(), text);
+}
+
+#[test]
+fn fixed_size_array() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Entity {
+        pos: [i32; 2],
+    }
+
+    equivalent! {
+        Entity { pos: [1, 2] },
+        Table(map! {
+            pos: Array(vec![
+                Integer(1),
+                Integer(2),
+            ])
+        }),
+    }
+}
+
+#[test]
+fn homogeneous_tuple() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Collection {
+        elems: (i64, i64, i64),
+    }
+
+    equivalent! {
+        Collection { elems: (0, 1, 2) },
+        Table(map! {
+            elems: Array(vec![
+                Integer(0),
+                Integer(1),
+                Integer(2),
+            ])
+        }),
+    }
+}
+
+#[test]
+fn homogeneous_tuple_struct() {
+    #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+    struct Object(Vec<String>, Vec<String>, Vec<String>);
+
+    equivalent! {
+        map! {
+            obj: Object(vec!["foo".to_string()], vec![], vec!["bar".to_string(), "baz".to_string()])
+        },
+        Table(map! {
+            obj: Array(vec![
+                Array(vec![
+                    Value::String("foo".to_string()),
+                ]),
+                Array(vec![]),
+                Array(vec![
+                    Value::String("bar".to_string()),
+                    Value::String("baz".to_string()),
+                ]),
+            ])
+        }),
+    }
+}
+
+#[test]
+fn json_interoperability() {
+    #[derive(Serialize, Deserialize)]
+    struct Foo {
+        any: toml::Value,
+    }
+
+    let _foo: Foo = serde_json::from_str(
+        r#"
+        {"any":1}
+    "#,
+    )
+    .unwrap();
+}
diff --git a/crates/test-suite/tests/spanned-impls.rs b/crates/test-suite/tests/spanned-impls.rs
new file mode 100644
index 00000000..cb12b1a1
--- /dev/null
+++ b/crates/test-suite/tests/spanned-impls.rs
@@ -0,0 +1,41 @@
+use std::cmp::{Ord, Ordering, PartialOrd};
+use toml::{from_str, Spanned};
+#[macro_use]
+extern crate serde_derive;
+
+#[test]
+fn test_spans_impls() {
+    #[derive(Deserialize)]
+    struct Foo {
+        bar: Spanned<bool>,
+        baz: Spanned<String>,
+    }
+    let f: Foo = from_str(
+        "
+    bar = true
+    baz = \"yes\"
+    ",
+    )
+    .unwrap();
+    let g: Foo = from_str(
+        "
+    baz = \"yes\"
+    bar = true
+    ",
+    )
+    .unwrap();
+    assert!(f.bar.span() != g.bar.span());
+    assert!(f.baz.span() != g.baz.span());
+
+    // test that eq still holds
+    assert_eq!(f.bar, g.bar);
+    assert_eq!(f.baz, g.baz);
+
+    // test that Ord returns equal order
+    assert_eq!(f.bar.cmp(&g.bar), Ordering::Equal);
+    assert_eq!(f.baz.cmp(&g.baz), Ordering::Equal);
+
+    // test that PartialOrd returns equal order
+    assert_eq!(f.bar.partial_cmp(&g.bar), Some(Ordering::Equal));
+    assert_eq!(f.baz.partial_cmp(&g.baz), Some(Ordering::Equal));
+}
diff --git a/crates/test-suite/tests/spanned.rs b/crates/test-suite/tests/spanned.rs
new file mode 100644
index 00000000..d8f7a129
--- /dev/null
+++ b/crates/test-suite/tests/spanned.rs
@@ -0,0 +1,249 @@
+extern crate serde;
+extern crate toml;
+#[macro_use]
+extern crate serde_derive;
+
+use std::collections::HashMap;
+use std::fmt::Debug;
+use toml::value::Datetime;
+use toml::Spanned;
+
+/// A set of good datetimes.
+pub fn good_datetimes() -> Vec<&'static str> {
+    let mut v = Vec::new();
+    v.push("1997-09-09T09:09:09Z");
+    v.push("1997-09-09T09:09:09+09:09");
+    v.push("1997-09-09T09:09:09-09:09");
+    v.push("1997-09-09T09:09:09");
+    v.push("1997-09-09");
+    v.push("09:09:09");
+    v.push("1997-09-09T09:09:09.09Z");
+    v.push("1997-09-09T09:09:09.09+09:09");
+    v.push("1997-09-09T09:09:09.09-09:09");
+    v.push("1997-09-09T09:09:09.09");
+    v.push("09:09:09.09");
+    v
+}
+
+#[test]
+fn test_spanned_field() {
+    #[derive(Deserialize)]
+    struct Foo<T> {
+        foo: Spanned<T>,
+    }
+
+    #[derive(Deserialize)]
+    struct BareFoo<T> {
+        foo: T,
+    }
+
+    fn good<'de, T>(s: &'de str, expected: &str, end: Option<usize>)
+    where
+        T: serde::Deserialize<'de> + Debug + PartialEq,
+    {
+        let foo: Foo<T> = toml::from_str(s).unwrap();
+
+        assert_eq!(6, foo.foo.start());
+        if let Some(end) = end {
+            assert_eq!(end, foo.foo.end());
+        } else {
+            assert_eq!(s.len(), foo.foo.end());
+        }
+        assert_eq!(expected, &s[foo.foo.start()..foo.foo.end()]);
+
+        // Test for Spanned<> at the top level
+        let foo_outer: Spanned<BareFoo<T>> = toml::from_str(s).unwrap();
+
+        assert_eq!(0, foo_outer.start());
+        assert_eq!(s.len(), foo_outer.end());
+        assert_eq!(foo.foo.into_inner(), foo_outer.into_inner().foo);
+    }
+
+    good::<String>("foo = \"foo\"", "\"foo\"", None);
+    good::<u32>("foo = 42", "42", None);
+    // leading plus
+    good::<u32>("foo = +42", "+42", None);
+    // table
+    good::<HashMap<String, u32>>(
+        "foo = {\"foo\" = 42, \"bar\" = 42}",
+        "{\"foo\" = 42, \"bar\" = 42}",
+        None,
+    );
+    // array
+    good::<Vec<u32>>("foo = [0, 1, 2, 3, 4]", "[0, 1, 2, 3, 4]", None);
+    // datetime
+    good::<String>(
+        "foo = \"1997-09-09T09:09:09Z\"",
+        "\"1997-09-09T09:09:09Z\"",
+        None,
+    );
+
+    for expected in good_datetimes() {
+        let s = format!("foo = {}", expected);
+        good::<Datetime>(&s, expected, None);
+    }
+    // ending at something other than the absolute end
+    good::<u32>("foo = 42\nnoise = true", "42", Some(8));
+}
+
+#[test]
+fn test_inner_spanned_table() {
+    #[derive(Deserialize)]
+    struct Foo {
+        foo: Spanned<HashMap<Spanned<String>, Spanned<String>>>,
+    }
+
+    fn good(s: &str, zero: bool) {
+        let foo: Foo = toml::from_str(s).unwrap();
+
+        if zero {
+            assert_eq!(foo.foo.start(), 0);
+            // We'd actually have to assert equality with s.len() here,
+            // but the current implementation doesn't support that,
+            // and it's not possible with toml's data format to support it
+            // in the general case as spans aren't always well-defined.
+            // So this check mainly serves as a reminder that this test should
+            // be updated *if* one day there is support for emitting the actual span.
+            assert_eq!(foo.foo.end(), 0);
+        } else {
+            assert_eq!(foo.foo.start(), s.find("{").unwrap());
+            assert_eq!(foo.foo.end(), s.find("}").unwrap() + 1);
+        }
+        for (k, v) in foo.foo.get_ref().iter() {
+            assert_eq!(&s[k.start()..k.end()], k.get_ref());
+            assert_eq!(&s[(v.start() + 1)..(v.end() - 1)], v.get_ref());
+        }
+    }
+
+    good(
+        "
+        [foo]
+        a = 'b'
+        bar = 'baz'
+        c = 'd'
+        e = \"f\"
+    ",
+        true,
+    );
+
+    good(
+        "
+        foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }",
+        false,
+    );
+}
+
+#[test]
+fn test_outer_spanned_table() {
+    #[derive(Deserialize)]
+    struct Foo {
+        foo: HashMap<Spanned<String>, Spanned<String>>,
+    }
+
+    fn good(s: &str) {
+        let foo: Foo = toml::from_str(s).unwrap();
+
+        for (k, v) in foo.foo.iter() {
+            assert_eq!(&s[k.start()..k.end()], k.get_ref());
+            assert_eq!(&s[(v.start() + 1)..(v.end() - 1)], v.get_ref());
+        }
+    }
+
+    good(
+        "
+        [foo]
+        a = 'b'
+        bar = 'baz'
+        c = 'd'
+        e = \"f\"
+    ",
+    );
+
+    good(
+        "
+        foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }
+    ",
+    );
+}
+
+#[test]
+fn test_spanned_nested() {
+    #[derive(Deserialize)]
+    struct Foo {
+        foo: HashMap<Spanned<String>, HashMap<Spanned<String>, Spanned<String>>>,
+    }
+
+    fn good(s: &str) {
+        let foo: Foo = toml::from_str(s).unwrap();
+
+        for (k, v) in foo.foo.iter() {
+            assert_eq!(&s[k.start()..k.end()], k.get_ref());
+            for (n_k, n_v) in v.iter() {
+                assert_eq!(&s[n_k.start()..n_k.end()], n_k.get_ref());
+                assert_eq!(&s[(n_v.start() + 1)..(n_v.end() - 1)], n_v.get_ref());
+            }
+        }
+    }
+
+    good(
+        "
+        [foo.a]
+        a = 'b'
+        c = 'd'
+        e = \"f\"
+        [foo.bar]
+        baz = 'true'
+    ",
+    );
+
+    good(
+        "
+        [foo]
+        foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }
+        bazz = {}
+        g = { h = 'i' }
+    ",
+    );
+}
+
+#[test]
+fn test_spanned_array() {
+    #[derive(Deserialize)]
+    struct Foo {
+        foo: Vec<Spanned<HashMap<Spanned<String>, Spanned<String>>>>,
+    }
+
+    fn good(s: &str) {
+        let foo_list: Foo = toml::from_str(s).unwrap();
+
+        for foo in foo_list.foo.iter() {
+            assert_eq!(foo.start(), 0);
+            // We'd actually have to assert equality with s.len() here,
+            // but the current implementation doesn't support that,
+            // and it's not possible with toml's data format to support it
+            // in the general case as spans aren't always well-defined.
+            // So this check mainly serves as a reminder that this test should
+            // be updated *if* one day there is support for emitting the actual span.
+            assert_eq!(foo.end(), 0);
+            for (k, v) in foo.get_ref().iter() {
+                assert_eq!(&s[k.start()..k.end()], k.get_ref());
+                assert_eq!(&s[(v.start() + 1)..(v.end() - 1)], v.get_ref());
+            }
+        }
+    }
+
+    good(
+        "
+        [[foo]]
+        a = 'b'
+        bar = 'baz'
+        c = 'd'
+        e = \"f\"
+        [[foo]]
+        a = 'c'
+        bar = 'baz'
+        c = 'g'
+        e = \"h\"
+    ",
+    );
+}
diff --git a/crates/test-suite/tests/tables-last.rs b/crates/test-suite/tests/tables-last.rs
new file mode 100644
index 00000000..b885a424
--- /dev/null
+++ b/crates/test-suite/tests/tables-last.rs
@@ -0,0 +1,32 @@
+#[macro_use]
+extern crate serde_derive;
+extern crate toml;
+
+use std::collections::HashMap;
+
+#[derive(Serialize)]
+struct A {
+    #[serde(serialize_with = "toml::ser::tables_last")]
+    vals: HashMap<&'static str, Value>,
+}
+
+#[derive(Serialize)]
+#[serde(untagged)]
+enum Value {
+    Map(HashMap<&'static str, &'static str>),
+    Int(i32),
+}
+
+#[test]
+fn always_works() {
+    let mut a = A {
+        vals: HashMap::new(),
+    };
+    a.vals.insert("foo", Value::Int(0));
+
+    let mut sub = HashMap::new();
+    sub.insert("foo", "bar");
+    a.vals.insert("bar", Value::Map(sub));
+
+    toml::to_string(&a).unwrap();
+}
diff --git a/crates/test-suite/tests/valid.rs b/crates/test-suite/tests/valid.rs
new file mode 100644
index 00000000..ad0dea91
--- /dev/null
+++ b/crates/test-suite/tests/valid.rs
@@ -0,0 +1,402 @@
+extern crate serde;
+extern crate serde_json;
+extern crate toml;
+
+use serde::ser::Serialize;
+use serde_json::Value as Json;
+use toml::{to_string_pretty, Value as Toml};
+
+fn to_json(toml: toml::Value) -> Json {
+    fn doit(s: &str, json: Json) -> Json {
+        let mut map = serde_json::Map::new();
+        map.insert("type".to_string(), Json::String(s.to_string()));
+        map.insert("value".to_string(), json);
+        Json::Object(map)
+    }
+
+    match toml {
+        Toml::String(s) => doit("string", Json::String(s)),
+        Toml::Integer(i) => doit("integer", Json::String(i.to_string())),
+        Toml::Float(f) => doit(
+            "float",
+            Json::String({
+                let s = format!("{:.15}", f);
+                let s = format!("{}", s.trim_end_matches('0'));
+                if s.ends_with('.') {
+                    format!("{}0", s)
+                } else {
+                    s
+                }
+            }),
+        ),
+        Toml::Boolean(b) => doit("bool", Json::String(format!("{}", b))),
+        Toml::Datetime(s) => doit("datetime", Json::String(s.to_string())),
+        Toml::Array(arr) => {
+            let is_table = match arr.first() {
+                Some(&Toml::Table(..)) => true,
+                _ => false,
+            };
+            let json = Json::Array(arr.into_iter().map(to_json).collect());
+            if is_table {
+                json
+            } else {
+                doit("array", json)
+            }
+        }
+        Toml::Table(table) => {
+            let mut map = serde_json::Map::new();
+            for (k, v) in table {
+                map.insert(k, to_json(v));
+            }
+            Json::Object(map)
+        }
+    }
+}
+
+fn run_pretty(toml: Toml) {
+    // Assert toml == json
+    println!("### pretty round trip parse.");
+
+    // standard pretty
+    let toml_raw = to_string_pretty(&toml).expect("to string");
+    let toml2 = toml_raw.parse().expect("from string");
+    assert_eq!(toml, toml2);
+
+    // pretty with indent 2
+    let mut result = String::with_capacity(128);
+    {
+        let mut serializer = toml::Serializer::pretty(&mut result);
+        serializer.pretty_array_indent(2);
+        toml.serialize(&mut serializer).expect("to string");
+    }
+    assert_eq!(toml, result.parse().expect("from str"));
+    result.clear();
+    {
+        let mut serializer = toml::Serializer::new(&mut result);
+        serializer.pretty_array_trailing_comma(false);
+        toml.serialize(&mut serializer).expect("to string");
+    }
+    assert_eq!(toml, result.parse().expect("from str"));
+    result.clear();
+    {
+        let mut serializer = toml::Serializer::pretty(&mut result);
+        serializer.pretty_string(false);
+        toml.serialize(&mut serializer).expect("to string");
+        assert_eq!(toml, toml2);
+    }
+    assert_eq!(toml, result.parse().expect("from str"));
+    result.clear();
+    {
+        let mut serializer = toml::Serializer::pretty(&mut result);
+        serializer.pretty_array(false);
+        toml.serialize(&mut serializer).expect("to string");
+        assert_eq!(toml, toml2);
+    }
+    assert_eq!(toml, result.parse().expect("from str"));
+}
+
+fn run(toml_raw: &str, json_raw: &str) {
+    println!("parsing:\n{}", toml_raw);
+    let toml: Toml = toml_raw.parse().unwrap();
+    let json: Json = json_raw.parse().unwrap();
+
+    // Assert toml == json
+    let toml_json = to_json(toml.clone());
+    assert!(
+        json == toml_json,
+        "expected\n{}\ngot\n{}\n",
+        serde_json::to_string_pretty(&json).unwrap(),
+        serde_json::to_string_pretty(&toml_json).unwrap()
+    );
+
+    // Assert round trip
+    println!("round trip parse: {}", toml);
+    let toml2 = toml.to_string().parse().unwrap();
+    assert_eq!(toml, toml2);
+    run_pretty(toml);
+}
+
+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!(
+    array_mixed_types_ints_and_floats,
+    include_str!("valid/array-mixed-types-ints-and-floats.toml"),
+    include_str!("valid/array-mixed-types-ints-and-floats.json")
+);
+test!(
+    array_mixed_types_arrays_and_ints,
+    include_str!("valid/array-mixed-types-arrays-and-ints.toml"),
+    include_str!("valid/array-mixed-types-arrays-and-ints.json")
+);
+test!(
+    array_mixed_types_strings_and_ints,
+    include_str!("valid/array-mixed-types-strings-and-ints.toml"),
+    include_str!("valid/array-mixed-types-strings-and-ints.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!(
+    comments_everywhere,
+    include_str!("valid/comments-everywhere.toml"),
+    include_str!("valid/comments-everywhere.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_space,
+    include_str!("valid/key-space.toml"),
+    include_str!("valid/key-space.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!(
+    key_empty,
+    include_str!("valid/key-empty.toml"),
+    include_str!("valid/key-empty.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!(
+    multiline_string,
+    include_str!("valid/multiline-string.toml"),
+    include_str!("valid/multiline-string.json")
+);
+test!(
+    raw_multiline_string,
+    include_str!("valid/raw-multiline-string.toml"),
+    include_str!("valid/raw-multiline-string.json")
+);
+test!(
+    raw_string,
+    include_str!("valid/raw-string.toml"),
+    include_str!("valid/raw-string.json")
+);
+test!(
+    string_empty,
+    include_str!("valid/string-empty.toml"),
+    include_str!("valid/string-empty.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_multi_empty,
+    include_str!("valid/table-multi-empty.toml"),
+    include_str!("valid/table-multi-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")
+);
+test!(
+    hard_example,
+    include_str!("valid/hard_example.toml"),
+    include_str!("valid/hard_example.json")
+);
+test!(
+    example2,
+    include_str!("valid/example2.toml"),
+    include_str!("valid/example2.json")
+);
+test!(
+    example3,
+    include_str!("valid/example-v0.3.0.toml"),
+    include_str!("valid/example-v0.3.0.json")
+);
+test!(
+    example4,
+    include_str!("valid/example-v0.4.0.toml"),
+    include_str!("valid/example-v0.4.0.json")
+);
+test!(
+    example_bom,
+    include_str!("valid/example-bom.toml"),
+    include_str!("valid/example.json")
+);
+
+test!(
+    datetime_truncate,
+    include_str!("valid/datetime-truncate.toml"),
+    include_str!("valid/datetime-truncate.json")
+);
+test!(
+    key_quote_newline,
+    include_str!("valid/key-quote-newline.toml"),
+    include_str!("valid/key-quote-newline.json")
+);
+test!(
+    table_array_nest_no_keys,
+    include_str!("valid/table-array-nest-no-keys.toml"),
+    include_str!("valid/table-array-nest-no-keys.json")
+);
+test!(
+    dotted_keys,
+    include_str!("valid/dotted-keys.toml"),
+    include_str!("valid/dotted-keys.json")
+);
+
+test!(
+    quote_surrounded_value,
+    include_str!("valid/quote-surrounded-value.toml"),
+    include_str!("valid/quote-surrounded-value.json")
+);
+
+test!(
+    float_exponent,
+    include_str!("valid/float-exponent.toml"),
+    include_str!("valid/float-exponent.json")
+);
+
+test!(
+    string_delim_end,
+    include_str!("valid/string-delim-end.toml"),
+    include_str!("valid/string-delim-end.json")
+);
diff --git a/crates/test-suite/tests/valid/array-empty.json b/crates/test-suite/tests/valid/array-empty.json
new file mode 100644
index 00000000..2fbf2567
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/array-empty.toml b/crates/test-suite/tests/valid/array-empty.toml
new file mode 100644
index 00000000..fa58dc63
--- /dev/null
+++ b/crates/test-suite/tests/valid/array-empty.toml
@@ -0,0 +1,1 @@
+thevoid = [[[[[]]]]]
diff --git a/crates/test-suite/tests/valid/array-mixed-types-arrays-and-ints.json b/crates/test-suite/tests/valid/array-mixed-types-arrays-and-ints.json
new file mode 100644
index 00000000..10074ec8
--- /dev/null
+++ b/crates/test-suite/tests/valid/array-mixed-types-arrays-and-ints.json
@@ -0,0 +1,11 @@
+{
+    "arrays-and-ints": {
+        "type": "array",
+        "value": [
+            {"type": "integer", "value": "1"},
+            {"type": "array", "value": [
+                { "type": "string", "value":"Arrays are not integers."}
+            ]}
+        ]
+    }
+}
diff --git a/crates/test-suite/tests/valid/array-mixed-types-arrays-and-ints.toml b/crates/test-suite/tests/valid/array-mixed-types-arrays-and-ints.toml
new file mode 100644
index 00000000..051ec731
--- /dev/null
+++ b/crates/test-suite/tests/valid/array-mixed-types-arrays-and-ints.toml
@@ -0,0 +1,1 @@
+arrays-and-ints =  [1, ["Arrays are not integers."]]
diff --git a/crates/test-suite/tests/valid/array-mixed-types-ints-and-floats.json b/crates/test-suite/tests/valid/array-mixed-types-ints-and-floats.json
new file mode 100644
index 00000000..c90665ea
--- /dev/null
+++ b/crates/test-suite/tests/valid/array-mixed-types-ints-and-floats.json
@@ -0,0 +1,9 @@
+{
+    "ints-and-floats": {
+        "type": "array",
+        "value": [
+            {"type": "integer", "value": "1"},
+            {"type": "float", "value": "1.1"}
+        ]
+    }
+}
diff --git a/crates/test-suite/tests/valid/array-mixed-types-ints-and-floats.toml b/crates/test-suite/tests/valid/array-mixed-types-ints-and-floats.toml
new file mode 100644
index 00000000..a5aa9b7a
--- /dev/null
+++ b/crates/test-suite/tests/valid/array-mixed-types-ints-and-floats.toml
@@ -0,0 +1,1 @@
+ints-and-floats = [1, 1.1]
diff --git a/crates/test-suite/tests/valid/array-mixed-types-strings-and-ints.json b/crates/test-suite/tests/valid/array-mixed-types-strings-and-ints.json
new file mode 100644
index 00000000..8ae322ed
--- /dev/null
+++ b/crates/test-suite/tests/valid/array-mixed-types-strings-and-ints.json
@@ -0,0 +1,9 @@
+{
+    "strings-and-ints": {
+        "type": "array",
+        "value": [
+            {"type": "string", "value": "hi"},
+            {"type": "integer", "value": "42"}
+        ]
+    }
+}
diff --git a/crates/test-suite/tests/valid/array-mixed-types-strings-and-ints.toml b/crates/test-suite/tests/valid/array-mixed-types-strings-and-ints.toml
new file mode 100644
index 00000000..f3483080
--- /dev/null
+++ b/crates/test-suite/tests/valid/array-mixed-types-strings-and-ints.toml
@@ -0,0 +1,1 @@
+strings-and-ints = ["hi", 42]
diff --git a/crates/test-suite/tests/valid/array-nospaces.json b/crates/test-suite/tests/valid/array-nospaces.json
new file mode 100644
index 00000000..1833d61c
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/array-nospaces.toml b/crates/test-suite/tests/valid/array-nospaces.toml
new file mode 100644
index 00000000..66189367
--- /dev/null
+++ b/crates/test-suite/tests/valid/array-nospaces.toml
@@ -0,0 +1,1 @@
+ints = [1,2,3]
diff --git a/crates/test-suite/tests/valid/arrays-hetergeneous.json b/crates/test-suite/tests/valid/arrays-hetergeneous.json
new file mode 100644
index 00000000..478fa5c7
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/arrays-hetergeneous.toml b/crates/test-suite/tests/valid/arrays-hetergeneous.toml
new file mode 100644
index 00000000..a246fcf1
--- /dev/null
+++ b/crates/test-suite/tests/valid/arrays-hetergeneous.toml
@@ -0,0 +1,1 @@
+mixed = [[1, 2], ["a", "b"], [1.1, 2.1]]
diff --git a/crates/test-suite/tests/valid/arrays-nested.json b/crates/test-suite/tests/valid/arrays-nested.json
new file mode 100644
index 00000000..d21920cc
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/arrays-nested.toml b/crates/test-suite/tests/valid/arrays-nested.toml
new file mode 100644
index 00000000..ce330224
--- /dev/null
+++ b/crates/test-suite/tests/valid/arrays-nested.toml
@@ -0,0 +1,1 @@
+nest = [["a"], ["b"]]
diff --git a/tests/fixtures/valid/arrays.json b/crates/test-suite/tests/valid/arrays.json
similarity index 100%
rename from tests/fixtures/valid/arrays.json
rename to crates/test-suite/tests/valid/arrays.json
diff --git a/tests/fixtures/valid/arrays.toml b/crates/test-suite/tests/valid/arrays.toml
similarity index 100%
rename from tests/fixtures/valid/arrays.toml
rename to crates/test-suite/tests/valid/arrays.toml
diff --git a/crates/test-suite/tests/valid/bool.json b/crates/test-suite/tests/valid/bool.json
new file mode 100644
index 00000000..ae368e94
--- /dev/null
+++ b/crates/test-suite/tests/valid/bool.json
@@ -0,0 +1,4 @@
+{
+    "f": {"type": "bool", "value": "false"},
+    "t": {"type": "bool", "value": "true"}
+}
diff --git a/crates/test-suite/tests/valid/bool.toml b/crates/test-suite/tests/valid/bool.toml
new file mode 100644
index 00000000..a8a829b3
--- /dev/null
+++ b/crates/test-suite/tests/valid/bool.toml
@@ -0,0 +1,2 @@
+t = true
+f = false
diff --git a/tests/fixtures/valid/comments-everywhere.json b/crates/test-suite/tests/valid/comments-everywhere.json
similarity index 100%
rename from tests/fixtures/valid/comments-everywhere.json
rename to crates/test-suite/tests/valid/comments-everywhere.json
diff --git a/crates/test-suite/tests/valid/comments-everywhere.toml b/crates/test-suite/tests/valid/comments-everywhere.toml
new file mode 100644
index 00000000..a13951d6
--- /dev/null
+++ b/crates/test-suite/tests/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
+# In between 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/crates/test-suite/tests/valid/datetime-truncate.json b/crates/test-suite/tests/valid/datetime-truncate.json
new file mode 100644
index 00000000..8c512e10
--- /dev/null
+++ b/crates/test-suite/tests/valid/datetime-truncate.json
@@ -0,0 +1,6 @@
+{
+    "bestdayever": {
+        "type": "datetime",
+        "value": "1987-07-05T17:45:00.123456789Z"
+    }
+}
diff --git a/crates/test-suite/tests/valid/datetime-truncate.toml b/crates/test-suite/tests/valid/datetime-truncate.toml
new file mode 100644
index 00000000..05de8410
--- /dev/null
+++ b/crates/test-suite/tests/valid/datetime-truncate.toml
@@ -0,0 +1,1 @@
+bestdayever = 1987-07-05T17:45:00.123456789012345Z
diff --git a/tests/fixtures/valid/datetime.json b/crates/test-suite/tests/valid/datetime.json
similarity index 100%
rename from tests/fixtures/valid/datetime.json
rename to crates/test-suite/tests/valid/datetime.json
diff --git a/tests/fixtures/valid/datetime.toml b/crates/test-suite/tests/valid/datetime.toml
similarity index 100%
rename from tests/fixtures/valid/datetime.toml
rename to crates/test-suite/tests/valid/datetime.toml
diff --git a/crates/test-suite/tests/valid/dotted-keys.json b/crates/test-suite/tests/valid/dotted-keys.json
new file mode 100644
index 00000000..cf9dd048
--- /dev/null
+++ b/crates/test-suite/tests/valid/dotted-keys.json
@@ -0,0 +1,34 @@
+{
+  "a": {
+    "b": {
+      "type": "integer",
+      "value": "123"
+    }
+  },
+  "table": {
+    "a": {
+      "b": {
+        "c": {
+          "type": "integer",
+          "value": "1"
+        },
+        "d": {
+          "type": "integer",
+          "value": "2"
+        }
+      }
+    },
+    "in": {
+      "type": {
+        "color": {
+          "type": "string",
+          "value": "blue"
+        },
+        "name": {
+          "type": "string",
+          "value": "cat"
+        }
+      }
+    }
+  }
+}
diff --git a/crates/test-suite/tests/valid/dotted-keys.toml b/crates/test-suite/tests/valid/dotted-keys.toml
new file mode 100644
index 00000000..234d64c8
--- /dev/null
+++ b/crates/test-suite/tests/valid/dotted-keys.toml
@@ -0,0 +1,7 @@
+a.b = 123
+
+[table]
+a.b.c = 1
+a  .  b  .  d = 2
+
+in = { type.name = "cat", type.color = "blue" }
diff --git a/crates/test-suite/tests/valid/empty.json b/crates/test-suite/tests/valid/empty.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/crates/test-suite/tests/valid/empty.json
@@ -0,0 +1,1 @@
+{}
diff --git a/crates/test-suite/tests/valid/empty.toml b/crates/test-suite/tests/valid/empty.toml
new file mode 100644
index 00000000..e69de29b
diff --git a/crates/test-suite/tests/valid/example-bom.toml b/crates/test-suite/tests/valid/example-bom.toml
new file mode 100644
index 00000000..fb5ac815
--- /dev/null
+++ b/crates/test-suite/tests/valid/example-bom.toml
@@ -0,0 +1,5 @@
+﻿best-day-ever = 1987-07-05T17:45:00Z
+
+[numtheory]
+boring = false
+perfection = [6, 28, 496]
diff --git a/crates/test-suite/tests/valid/example-v0.3.0.json b/crates/test-suite/tests/valid/example-v0.3.0.json
new file mode 100644
index 00000000..1d9dcb58
--- /dev/null
+++ b/crates/test-suite/tests/valid/example-v0.3.0.json
@@ -0,0 +1,1 @@
+{"Array":{"key1":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key2":{"type":"array","value":[{"type":"string","value":"red"},{"type":"string","value":"yellow"},{"type":"string","value":"green"}]},"key3":{"type":"array","value":[{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]},{"type":"array","value":[{"type":"integer","value":"3"},{"type":"integer","value":"4"},{"type":"integer","value":"5"}]}]},"key4":{"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":"string","value":"c"}]}]},"key5":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key6":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]}},"Booleans":{"False":{"type":"bool","value":"false"},"True":{"type":"bool","value":"true"}},"Datetime":{"key1":{"type":"datetime","value":"1979-05-27T07:32:00Z"}},"Float":{"both":{},"exponent":{},"fractional":{"key1":{"type":"float","value":"1.0"},"key2":{"type":"float","value":"3.1415"},"key3":{"type":"float","value":"-0.01"}}},"Integer":{"key1":{"type":"integer","value":"99"},"key2":{"type":"integer","value":"42"},"key3":{"type":"integer","value":"0"},"key4":{"type":"integer","value":"-17"}},"String":{"Literal":{"Multiline":{"lines":{"type":"string","value":"The first newline is\ntrimmed in raw strings.\n   All other whitespace\n   is preserved.\n"},"regex2":{"type":"string","value":"I [dw]on't need \\d{2} apples"}},"quoted":{"type":"string","value":"Tom \"Dubs\" Preston-Werner"},"regex":{"type":"string","value":"\u003c\\i\\c*\\s*\u003e"},"winpath":{"type":"string","value":"C:\\Users\\nodejs\\templates"},"winpath2":{"type":"string","value":"\\\\ServerX\\admin$\\system32\\"}},"Multiline":{"key1":{"type":"string","value":"One\nTwo"},"key2":{"type":"string","value":"One\nTwo"},"key3":{"type":"string","value":"One\nTwo"}},"Multilined":{"Singleline":{"key1":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key2":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key3":{"type":"string","value":"The quick brown fox jumps over the lazy dog."}}},"basic":{"type":"string","value":"I'm a string. \"You can quote me\". Name\u0009José\nLocation\u0009SF."}},"Table":{"key":{"type":"string","value":"value"}},"dog":{"tater":{"type":{"type":"string","value":"pug"}}},"fruit":[{"name":{"type":"string","value":"apple"},"physical":{"color":{"type":"string","value":"red"},"shape":{"type":"string","value":"round"}},"variety":[{"name":{"type":"string","value":"red delicious"}},{"name":{"type":"string","value":"granny smith"}}]},{"name":{"type":"string","value":"banana"},"variety":[{"name":{"type":"string","value":"plantain"}}]}],"products":[{"name":{"type":"string","value":"Hammer"},"sku":{"type":"integer","value":"738594937"}},{},{"color":{"type":"string","value":"gray"},"name":{"type":"string","value":"Nail"},"sku":{"type":"integer","value":"284758393"}}],"x":{"y":{"z":{"w":{}}}}}
diff --git a/crates/test-suite/tests/valid/example-v0.3.0.toml b/crates/test-suite/tests/valid/example-v0.3.0.toml
new file mode 100644
index 00000000..76aacc31
--- /dev/null
+++ b/crates/test-suite/tests/valid/example-v0.3.0.toml
@@ -0,0 +1,182 @@
+# Comment
+# I am a comment. Hear me roar. Roar.
+
+# Table
+# Tables (also known as hash tables or dictionaries) are collections of key/value pairs.
+# They appear in square brackets on a line by themselves.
+
+[Table]
+
+key = "value" # Yeah, you can do this.
+
+# Nested tables are denoted by table names with dots in them. Name your tables whatever crap you please, just don't use #, ., [ or ].
+
+[dog.tater]
+type = "pug"
+
+# You don't need to specify all the super-tables if you don't want to. TOML knows how to do it for you.
+
+# [x] you
+# [x.y] don't
+# [x.y.z] need these
+[x.y.z.w] # for this to work
+
+# String
+# There are four ways to express strings: basic, multi-line basic, literal, and multi-line literal.
+# All strings must contain only valid UTF-8 characters.
+
+[String]
+basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
+
+[String.Multiline]
+
+# The following strings are byte-for-byte equivalent:
+key1 = "One\nTwo"
+key2 = """One\nTwo"""
+key3 = """
+One
+Two"""
+
+[String.Multilined.Singleline]
+
+# The following strings are byte-for-byte equivalent:
+key1 = "The quick brown fox jumps over the lazy dog."
+
+key2 = """
+The quick brown \
+
+
+  fox jumps over \
+    the lazy dog."""
+
+key3 = """\
+       The quick brown \
+       fox jumps over \
+       the lazy dog.\
+       """
+
+[String.Literal]
+
+# What you see is what you get.
+winpath  = 'C:\Users\nodejs\templates'
+winpath2 = '\\ServerX\admin$\system32\'
+quoted   = 'Tom "Dubs" Preston-Werner'
+regex    = '<\i\c*\s*>'
+
+
+[String.Literal.Multiline]
+
+regex2 = '''I [dw]on't need \d{2} apples'''
+lines  = '''
+The first newline is
+trimmed in raw strings.
+   All other whitespace
+   is preserved.
+'''
+
+# Integer
+# Integers are whole numbers. Positive numbers may be prefixed with a plus sign.
+# Negative numbers are prefixed with a minus sign.
+
+[Integer]
+key1 = +99
+key2 = 42
+key3 = 0
+key4 = -17
+
+# Float
+# A float consists of an integer part (which may be prefixed with a plus or minus sign)
+# followed by a fractional part and/or an exponent part.
+
+[Float.fractional]
+
+# fractional
+key1 = +1.0
+key2 = 3.1415
+key3 = -0.01
+
+[Float.exponent]
+
+# exponent
+#key1 = 5e+22
+#key2 = 1e6
+#key3 = -2E-2
+
+[Float.both]
+
+# both
+#key = 6.626e-34
+
+# Boolean
+# Booleans are just the tokens you're used to. Always lowercase.
+
+[Booleans]
+True = true
+False = false
+
+# Datetime
+# Datetimes are RFC 3339 dates.
+
+[Datetime]
+key1 = 1979-05-27T07:32:00Z
+#key2 = 1979-05-27T00:32:00-07:00
+#key3 = 1979-05-27T00:32:00.999999-07:00
+
+# Array
+# Arrays are square brackets with other primitives inside. Whitespace is ignored. Elements are separated by commas. Data types may not be mixed.
+
+[Array]
+key1 = [ 1, 2, 3 ]
+key2 = [ "red", "yellow", "green" ]
+key3 = [ [ 1, 2 ], [3, 4, 5] ]
+key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok
+
+#Arrays can also be multiline. So in addition to ignoring whitespace, arrays also ignore newlines between the brackets.
+# Terminating commas are ok before the closing bracket.
+
+key5 = [
+  1, 2, 3
+]
+key6 = [
+  1,
+  2, # this is ok
+]
+
+# Array of Tables
+# These can be expressed by using a table name in double brackets.
+# Each table with the same double bracketed name will be an element in the array.
+# The tables are inserted in the order encountered.
+
+[[products]]
+name = "Hammer"
+sku = 738594937
+
+[[products]]
+
+[[products]]
+name = "Nail"
+sku = 284758393
+color = "gray"
+
+
+# You can create nested arrays of tables as well.
+
+[[fruit]]
+  name = "apple"
+
+  [fruit.physical]
+    color = "red"
+    shape = "round"
+
+  [[fruit.variety]]
+    name = "red delicious"
+
+  [[fruit.variety]]
+    name = "granny smith"
+
+[[fruit]]
+  name = "banana"
+
+  [[fruit.variety]]
+    name = "plantain"
+
diff --git a/crates/test-suite/tests/valid/example-v0.4.0.json b/crates/test-suite/tests/valid/example-v0.4.0.json
new file mode 100644
index 00000000..d5cac343
--- /dev/null
+++ b/crates/test-suite/tests/valid/example-v0.4.0.json
@@ -0,0 +1,1 @@
+{"array":{"key1":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key2":{"type":"array","value":[{"type":"string","value":"red"},{"type":"string","value":"yellow"},{"type":"string","value":"green"}]},"key3":{"type":"array","value":[{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]},{"type":"array","value":[{"type":"integer","value":"3"},{"type":"integer","value":"4"},{"type":"integer","value":"5"}]}]},"key4":{"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":"string","value":"c"}]}]},"key5":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"},{"type":"integer","value":"3"}]},"key6":{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]}},"boolean":{"False":{"type":"bool","value":"false"},"True":{"type":"bool","value":"true"}},"datetime":{},"float":{"both":{},"exponent":{},"fractional":{"key1":{"type":"float","value":"1.0"},"key2":{"type":"float","value":"3.1415"},"key3":{"type":"float","value":"-0.01"}},"underscores":{}},"fruit":[{"name":{"type":"string","value":"apple"},"physical":{"color":{"type":"string","value":"red"},"shape":{"type":"string","value":"round"}},"variety":[{"name":{"type":"string","value":"red delicious"}},{"name":{"type":"string","value":"granny smith"}}]},{"name":{"type":"string","value":"banana"},"variety":[{"name":{"type":"string","value":"plantain"}}]}],"integer":{"key1":{"type":"integer","value":"99"},"key2":{"type":"integer","value":"42"},"key3":{"type":"integer","value":"0"},"key4":{"type":"integer","value":"-17"},"underscores":{"key1":{"type":"integer","value":"1000"},"key2":{"type":"integer","value":"5349221"},"key3":{"type":"integer","value":"12345"}}},"products":[{"name":{"type":"string","value":"Hammer"},"sku":{"type":"integer","value":"738594937"}},{},{"color":{"type":"string","value":"gray"},"name":{"type":"string","value":"Nail"},"sku":{"type":"integer","value":"284758393"}}],"string":{"basic":{"basic":{"type":"string","value":"I'm a string. \"You can quote me\". Name\u0009José\nLocation\u0009SF."}},"literal":{"multiline":{"lines":{"type":"string","value":"The first newline is\ntrimmed in raw strings.\n   All other whitespace\n   is preserved.\n"},"regex2":{"type":"string","value":"I [dw]on't need \\d{2} apples"}},"quoted":{"type":"string","value":"Tom \"Dubs\" Preston-Werner"},"regex":{"type":"string","value":"\u003c\\i\\c*\\s*\u003e"},"winpath":{"type":"string","value":"C:\\Users\\nodejs\\templates"},"winpath2":{"type":"string","value":"\\\\ServerX\\admin$\\system32\\"}},"multiline":{"continued":{"key1":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key2":{"type":"string","value":"The quick brown fox jumps over the lazy dog."},"key3":{"type":"string","value":"The quick brown fox jumps over the lazy dog."}},"key1":{"type":"string","value":"One\nTwo"},"key2":{"type":"string","value":"One\nTwo"},"key3":{"type":"string","value":"One\nTwo"}}},"table":{"inline":{"name":{"first":{"type":"string","value":"Tom"},"last":{"type":"string","value":"Preston-Werner"}},"point":{"x":{"type":"integer","value":"1"},"y":{"type":"integer","value":"2"}}},"key":{"type":"string","value":"value"},"subtable":{"key":{"type":"string","value":"another value"}}},"x":{"y":{"z":{"w":{}}}}}
diff --git a/crates/test-suite/tests/valid/example-v0.4.0.toml b/crates/test-suite/tests/valid/example-v0.4.0.toml
new file mode 100644
index 00000000..69f1c1b0
--- /dev/null
+++ b/crates/test-suite/tests/valid/example-v0.4.0.toml
@@ -0,0 +1,236 @@
+################################################################################
+## Comment
+
+# Speak your mind with the hash symbol. They go from the symbol to the end of
+# the line.
+
+
+################################################################################
+## Table
+
+# Tables (also known as hash tables or dictionaries) are collections of
+# key/value pairs. They appear in square brackets on a line by themselves.
+
+[table]
+
+key = "value" # Yeah, you can do this.
+
+# Nested tables are denoted by table names with dots in them. Name your tables
+# whatever crap you please, just don't use #, ., [ or ].
+
+[table.subtable]
+
+key = "another value"
+
+# You don't need to specify all the super-tables if you don't want to. TOML
+# knows how to do it for you.
+
+# [x] you
+# [x.y] don't
+# [x.y.z] need these
+[x.y.z.w] # for this to work
+
+
+################################################################################
+## Inline Table
+
+# Inline tables provide a more compact syntax for expressing tables. They are
+# especially useful for grouped data that can otherwise quickly become verbose.
+# Inline tables are enclosed in curly braces `{` and `}`. No newlines are
+# allowed between the curly braces unless they are valid within a value.
+
+[table.inline]
+
+name = { first = "Tom", last = "Preston-Werner" }
+point = { x = 1, y = 2 }
+
+
+################################################################################
+## String
+
+# There are four ways to express strings: basic, multi-line basic, literal, and
+# multi-line literal. All strings must contain only valid UTF-8 characters.
+
+[string.basic]
+
+basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
+
+[string.multiline]
+
+# The following strings are byte-for-byte equivalent:
+key1 = "One\nTwo"
+key2 = """One\nTwo"""
+key3 = """
+One
+Two"""
+
+[string.multiline.continued]
+
+# The following strings are byte-for-byte equivalent:
+key1 = "The quick brown fox jumps over the lazy dog."
+
+key2 = """
+The quick brown \
+
+
+  fox jumps over \
+    the lazy dog."""
+
+key3 = """\
+       The quick brown \
+       fox jumps over \
+       the lazy dog.\
+       """
+
+[string.literal]
+
+# What you see is what you get.
+winpath  = 'C:\Users\nodejs\templates'
+winpath2 = '\\ServerX\admin$\system32\'
+quoted   = 'Tom "Dubs" Preston-Werner'
+regex    = '<\i\c*\s*>'
+
+
+[string.literal.multiline]
+
+regex2 = '''I [dw]on't need \d{2} apples'''
+lines  = '''
+The first newline is
+trimmed in raw strings.
+   All other whitespace
+   is preserved.
+'''
+
+
+################################################################################
+## Integer
+
+# Integers are whole numbers. Positive numbers may be prefixed with a plus sign.
+# Negative numbers are prefixed with a minus sign.
+
+[integer]
+
+key1 = +99
+key2 = 42
+key3 = 0
+key4 = -17
+
+[integer.underscores]
+
+# For large numbers, you may use underscores to enhance readability. Each
+# underscore must be surrounded by at least one digit.
+key1 = 1_000
+key2 = 5_349_221
+key3 = 1_2_3_4_5     # valid but inadvisable
+
+
+################################################################################
+## Float
+
+# A float consists of an integer part (which may be prefixed with a plus or
+# minus sign) followed by a fractional part and/or an exponent part.
+
+[float.fractional]
+
+key1 = +1.0
+key2 = 3.1415
+key3 = -0.01
+
+[float.exponent]
+
+[float.both]
+
+[float.underscores]
+
+
+################################################################################
+## Boolean
+
+# Booleans are just the tokens you're used to. Always lowercase.
+
+[boolean]
+
+True = true
+False = false
+
+
+################################################################################
+## Datetime
+
+# Datetimes are RFC 3339 dates.
+
+[datetime]
+
+#key1 = 1979-05-27T07:32:00Z
+#key2 = 1979-05-27T00:32:00-07:00
+#key3 = 1979-05-27T00:32:00.999999-07:00
+
+
+################################################################################
+## Array
+
+# Arrays are square brackets with other primitives inside. Whitespace is
+# ignored. Elements are separated by commas. Since 2019-11-06 data types can be
+# mixed.
+
+[array]
+
+key1 = [ 1, 2, 3 ]
+key2 = [ "red", "yellow", "green" ]
+key3 = [ [ 1, 2 ], [3, 4, 5] ]
+key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok
+
+# Arrays can also be multiline. So in addition to ignoring whitespace, arrays
+# also ignore newlines between the brackets.  Terminating commas are ok before
+# the closing bracket.
+
+key5 = [
+  1, 2, 3
+]
+key6 = [
+  1,
+  2, # this is ok
+]
+
+
+################################################################################
+## Array of Tables
+
+# These can be expressed by using a table name in double brackets. Each table
+# with the same double bracketed name will be an element in the array. The
+# tables are inserted in the order encountered.
+
+[[products]]
+
+name = "Hammer"
+sku = 738594937
+
+[[products]]
+
+[[products]]
+
+name = "Nail"
+sku = 284758393
+color = "gray"
+
+
+# You can create nested arrays of tables as well.
+
+[[fruit]]
+  name = "apple"
+
+  [fruit.physical]
+    color = "red"
+    shape = "round"
+
+  [[fruit.variety]]
+    name = "red delicious"
+
+  [[fruit.variety]]
+    name = "granny smith"
+
+[[fruit]]
+  name = "banana"
+
+  [[fruit.variety]]
+    name = "plantain"
diff --git a/crates/test-suite/tests/valid/example.json b/crates/test-suite/tests/valid/example.json
new file mode 100644
index 00000000..48aa9078
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/example.toml b/crates/test-suite/tests/valid/example.toml
new file mode 100644
index 00000000..8cb02e01
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/example2.json b/crates/test-suite/tests/valid/example2.json
new file mode 100644
index 00000000..3249a974
--- /dev/null
+++ b/crates/test-suite/tests/valid/example2.json
@@ -0,0 +1,1 @@
+{"clients":{"data":{"type":"array","value":[{"type":"array","value":[{"type":"string","value":"gamma"},{"type":"string","value":"delta"}]},{"type":"array","value":[{"type":"integer","value":"1"},{"type":"integer","value":"2"}]}]},"hosts":{"type":"array","value":[{"type":"string","value":"alpha"},{"type":"string","value":"omega"}]}},"database":{"connection_max":{"type":"integer","value":"5000"},"enabled":{"type":"bool","value":"true"},"ports":{"type":"array","value":[{"type":"integer","value":"8001"},{"type":"integer","value":"8001"},{"type":"integer","value":"8002"}]},"server":{"type":"string","value":"192.168.1.1"}},"owner":{"bio":{"type":"string","value":"GitHub Cofounder \u0026 CEO\nLikes tater tots and beer."},"dob":{"type":"datetime","value":"1979-05-27T07:32:00Z"},"name":{"type":"string","value":"Tom Preston-Werner"},"organization":{"type":"string","value":"GitHub"}},"products":[{"name":{"type":"string","value":"Hammer"},"sku":{"type":"integer","value":"738594937"}},{"color":{"type":"string","value":"gray"},"name":{"type":"string","value":"Nail"},"sku":{"type":"integer","value":"284758393"}}],"servers":{"alpha":{"dc":{"type":"string","value":"eqdc10"},"ip":{"type":"string","value":"10.0.0.1"}},"beta":{"country":{"type":"string","value":"中国"},"dc":{"type":"string","value":"eqdc10"},"ip":{"type":"string","value":"10.0.0.2"}}},"title":{"type":"string","value":"TOML Example"}}
diff --git a/crates/test-suite/tests/valid/example2.toml b/crates/test-suite/tests/valid/example2.toml
new file mode 100644
index 00000000..bc12c990
--- /dev/null
+++ b/crates/test-suite/tests/valid/example2.toml
@@ -0,0 +1,47 @@
+# This is a TOML document. Boom.
+
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers]
+
+  # You can indent as you please. Tabs or spaces. TOML don't care.
+  [servers.alpha]
+  ip = "10.0.0.1"
+  dc = "eqdc10"
+
+  [servers.beta]
+  ip = "10.0.0.2"
+  dc = "eqdc10"
+  country = "中国" # This should be parsed as UTF-8
+
+[clients]
+data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
+
+# Line breaks are OK when inside arrays
+hosts = [
+  "alpha",
+  "omega"
+]
+
+# Products
+
+  [[products]]
+  name = "Hammer"
+  sku = 738594937
+
+  [[products]]
+  name = "Nail"
+  sku = 284758393
+  color = "gray"
diff --git a/crates/test-suite/tests/valid/float-exponent.json b/crates/test-suite/tests/valid/float-exponent.json
new file mode 100644
index 00000000..97709b42
--- /dev/null
+++ b/crates/test-suite/tests/valid/float-exponent.json
@@ -0,0 +1,11 @@
+{
+    "lower": {"type": "float", "value": "300.0"},
+    "upper": {"type": "float", "value": "300.0"},
+    "neg": {"type": "float", "value": "0.03"},
+    "pos": {"type": "float", "value": "300.0"},
+    "zero": {"type": "float", "value": "3.0"},
+    "pointlower": {"type": "float", "value": "310.0"},
+    "pointupper": {"type": "float", "value": "310.0"},
+    "prefix-zero-exp": {"type": "float", "value": "1000000.0"},
+    "prefix-zero-exp-plus": {"type": "float", "value": "1000000.0"}
+}
diff --git a/crates/test-suite/tests/valid/float-exponent.toml b/crates/test-suite/tests/valid/float-exponent.toml
new file mode 100644
index 00000000..5349db36
--- /dev/null
+++ b/crates/test-suite/tests/valid/float-exponent.toml
@@ -0,0 +1,9 @@
+lower = 3e2
+upper = 3E2
+neg = 3e-2
+pos = 3E+2
+zero = 3e0
+pointlower = 3.1e2
+pointupper = 3.1E2
+prefix-zero-exp = 1e06
+prefix-zero-exp-plus = 1e+06
diff --git a/crates/test-suite/tests/valid/float.json b/crates/test-suite/tests/valid/float.json
new file mode 100644
index 00000000..b8a2e975
--- /dev/null
+++ b/crates/test-suite/tests/valid/float.json
@@ -0,0 +1,4 @@
+{
+    "pi": {"type": "float", "value": "3.14"},
+    "negpi": {"type": "float", "value": "-3.14"}
+}
diff --git a/crates/test-suite/tests/valid/float.toml b/crates/test-suite/tests/valid/float.toml
new file mode 100644
index 00000000..7c528d20
--- /dev/null
+++ b/crates/test-suite/tests/valid/float.toml
@@ -0,0 +1,2 @@
+pi = 3.14
+negpi = -3.14
diff --git a/crates/test-suite/tests/valid/hard_example.json b/crates/test-suite/tests/valid/hard_example.json
new file mode 100644
index 00000000..9762e58e
--- /dev/null
+++ b/crates/test-suite/tests/valid/hard_example.json
@@ -0,0 +1,1 @@
+{"the":{"hard":{"another_test_string":{"type":"string","value":" Same thing, but with a string #"},"bit#":{"multi_line_array":{"type":"array","value":[{"type":"string","value":"]"}]},"what?":{"type":"string","value":"You don't think some user won't do that?"}},"harder_test_string":{"type":"string","value":" And when \"'s are in the string, along with # \""},"test_array":{"type":"array","value":[{"type":"string","value":"] "},{"type":"string","value":" # "}]},"test_array2":{"type":"array","value":[{"type":"string","value":"Test #11 ]proved that"},{"type":"string","value":"Experiment #9 was a success"}]}},"test_string":{"type":"string","value":"You'll hate me after this - #"}}}
diff --git a/crates/test-suite/tests/valid/hard_example.toml b/crates/test-suite/tests/valid/hard_example.toml
new file mode 100644
index 00000000..38856c87
--- /dev/null
+++ b/crates/test-suite/tests/valid/hard_example.toml
@@ -0,0 +1,33 @@
+# Test file for TOML
+# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
+# This part you'll really hate
+
+[the]
+test_string = "You'll hate me after this - #"          # " Annoying, isn't it?
+
+    [the.hard]
+    test_array = [ "] ", " # "]      # ] There you go, parse this!
+    test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
+    # You didn't think it'd as easy as chucking out the last #, did you?
+    another_test_string = " Same thing, but with a string #"
+    harder_test_string = " And when \"'s are in the string, along with # \""   # "and comments are there too"
+    # Things will get harder
+
+        [the.hard."bit#"]
+        "what?" = "You don't think some user won't do that?"
+        multi_line_array = [
+            "]",
+            # ] Oh yes I did
+            ]
+
+# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test
+
+#[error]   if you didn't catch this, your parser is broken
+#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment"   like this
+#array = [
+#         "This might most likely happen in multiline arrays",
+#         Like here,
+#         "or here,
+#         and here"
+#         ]     End of array comment, forgot the #
+#number = 3.14  pi <--again forgot the #
diff --git a/crates/test-suite/tests/valid/implicit-and-explicit-after.json b/crates/test-suite/tests/valid/implicit-and-explicit-after.json
new file mode 100644
index 00000000..374bd093
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/implicit-and-explicit-after.toml b/crates/test-suite/tests/valid/implicit-and-explicit-after.toml
new file mode 100644
index 00000000..c0e8865b
--- /dev/null
+++ b/crates/test-suite/tests/valid/implicit-and-explicit-after.toml
@@ -0,0 +1,5 @@
+[a.b.c]
+answer = 42
+
+[a]
+better = 43
diff --git a/crates/test-suite/tests/valid/implicit-and-explicit-before.json b/crates/test-suite/tests/valid/implicit-and-explicit-before.json
new file mode 100644
index 00000000..374bd093
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/implicit-and-explicit-before.toml b/crates/test-suite/tests/valid/implicit-and-explicit-before.toml
new file mode 100644
index 00000000..eee68ff5
--- /dev/null
+++ b/crates/test-suite/tests/valid/implicit-and-explicit-before.toml
@@ -0,0 +1,5 @@
+[a]
+better = 43
+
+[a.b.c]
+answer = 42
diff --git a/crates/test-suite/tests/valid/implicit-groups.json b/crates/test-suite/tests/valid/implicit-groups.json
new file mode 100644
index 00000000..fbae7fc7
--- /dev/null
+++ b/crates/test-suite/tests/valid/implicit-groups.json
@@ -0,0 +1,9 @@
+{
+    "a": {
+        "b": {
+            "c": {
+                "answer": {"type": "integer", "value": "42"}
+            }
+        }
+    }
+}
diff --git a/crates/test-suite/tests/valid/implicit-groups.toml b/crates/test-suite/tests/valid/implicit-groups.toml
new file mode 100644
index 00000000..b6333e49
--- /dev/null
+++ b/crates/test-suite/tests/valid/implicit-groups.toml
@@ -0,0 +1,2 @@
+[a.b.c]
+answer = 42
diff --git a/crates/test-suite/tests/valid/integer.json b/crates/test-suite/tests/valid/integer.json
new file mode 100644
index 00000000..77ecb6cc
--- /dev/null
+++ b/crates/test-suite/tests/valid/integer.json
@@ -0,0 +1,14 @@
+{
+    "answer": {"type": "integer", "value": "42"},
+    "neganswer": {"type": "integer", "value": "-42"},
+
+    "neg_zero": {"type": "integer", "value": "0"},
+    "pos_zero": {"type": "integer", "value": "0"},
+
+    "hex1": {"type": "integer", "value": "3735928559"},
+    "hex2": {"type": "integer", "value": "3735928559"},
+    "hex3": {"type": "integer", "value": "3735928559"},
+    "oct1": {"type": "integer", "value": "342391"},
+    "oct2": {"type": "integer", "value": "493"},
+    "bin1": {"type": "integer", "value": "214"}
+}
diff --git a/crates/test-suite/tests/valid/integer.toml b/crates/test-suite/tests/valid/integer.toml
new file mode 100644
index 00000000..8362459b
--- /dev/null
+++ b/crates/test-suite/tests/valid/integer.toml
@@ -0,0 +1,18 @@
+answer = 42
+neganswer = -42
+
+neg_zero = -0
+pos_zero = +0
+
+# hexadecimal with prefix `0x`
+hex1 = 0xDEADBEEF
+hex2 = 0xdeadbeef
+hex3 = 0xdead_beef
+
+# octal with prefix `0o`
+oct1 = 0o01234567
+oct2 = 0o755 # useful for Unix file permissions
+
+# binary with prefix `0b`
+bin1 = 0b11010110
+
diff --git a/crates/test-suite/tests/valid/key-empty.json b/crates/test-suite/tests/valid/key-empty.json
new file mode 100644
index 00000000..99afee47
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-empty.json
@@ -0,0 +1,3 @@
+{
+  "": {"type": "integer", "value": "1"}
+}
diff --git a/crates/test-suite/tests/valid/key-empty.toml b/crates/test-suite/tests/valid/key-empty.toml
new file mode 100644
index 00000000..2f6a07c2
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-empty.toml
@@ -0,0 +1,1 @@
+"" = 1
diff --git a/crates/test-suite/tests/valid/key-equals-nospace.json b/crates/test-suite/tests/valid/key-equals-nospace.json
new file mode 100644
index 00000000..1f8709ab
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-equals-nospace.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "integer", "value": "42"}
+}
diff --git a/crates/test-suite/tests/valid/key-equals-nospace.toml b/crates/test-suite/tests/valid/key-equals-nospace.toml
new file mode 100644
index 00000000..560901c5
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-equals-nospace.toml
@@ -0,0 +1,1 @@
+answer=42
diff --git a/crates/test-suite/tests/valid/key-quote-newline.json b/crates/test-suite/tests/valid/key-quote-newline.json
new file mode 100644
index 00000000..12473e42
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-quote-newline.json
@@ -0,0 +1,3 @@
+{
+    "\n": {"type": "integer", "value": "1"}
+}
diff --git a/crates/test-suite/tests/valid/key-quote-newline.toml b/crates/test-suite/tests/valid/key-quote-newline.toml
new file mode 100644
index 00000000..a2639bfb
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-quote-newline.toml
@@ -0,0 +1,1 @@
+"\n" = 1
diff --git a/crates/test-suite/tests/valid/key-space.json b/crates/test-suite/tests/valid/key-space.json
new file mode 100644
index 00000000..9d1f7691
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-space.json
@@ -0,0 +1,3 @@
+{
+    "a b": {"type": "integer", "value": "1"}
+}
diff --git a/crates/test-suite/tests/valid/key-space.toml b/crates/test-suite/tests/valid/key-space.toml
new file mode 100644
index 00000000..f4f36c4f
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-space.toml
@@ -0,0 +1,1 @@
+"a b" = 1
diff --git a/crates/test-suite/tests/valid/key-special-chars.json b/crates/test-suite/tests/valid/key-special-chars.json
new file mode 100644
index 00000000..6550ebda
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-special-chars.json
@@ -0,0 +1,5 @@
+{
+    "~!@#$^&*()_+-`1234567890[]\\|/?><.,;:'": {
+        "type": "integer", "value": "1"
+    }
+}
diff --git a/crates/test-suite/tests/valid/key-special-chars.toml b/crates/test-suite/tests/valid/key-special-chars.toml
new file mode 100644
index 00000000..dc43625d
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-special-chars.toml
@@ -0,0 +1,1 @@
+"~!@#$^&*()_+-`1234567890[]\\|/?><.,;:'" = 1
diff --git a/crates/test-suite/tests/valid/key-with-pound.json b/crates/test-suite/tests/valid/key-with-pound.json
new file mode 100644
index 00000000..ee39e1de
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-with-pound.json
@@ -0,0 +1,3 @@
+{
+    "key#name": {"type": "integer", "value": "5"}
+}
diff --git a/crates/test-suite/tests/valid/key-with-pound.toml b/crates/test-suite/tests/valid/key-with-pound.toml
new file mode 100644
index 00000000..65b766fd
--- /dev/null
+++ b/crates/test-suite/tests/valid/key-with-pound.toml
@@ -0,0 +1,1 @@
+"key#name" = 5
diff --git a/crates/test-suite/tests/valid/long-float.json b/crates/test-suite/tests/valid/long-float.json
new file mode 100644
index 00000000..8ceed479
--- /dev/null
+++ b/crates/test-suite/tests/valid/long-float.json
@@ -0,0 +1,4 @@
+{
+    "longpi": {"type": "float", "value": "3.141592653589793"},
+    "neglongpi": {"type": "float", "value": "-3.141592653589793"}
+}
diff --git a/crates/test-suite/tests/valid/long-float.toml b/crates/test-suite/tests/valid/long-float.toml
new file mode 100644
index 00000000..9558ae47
--- /dev/null
+++ b/crates/test-suite/tests/valid/long-float.toml
@@ -0,0 +1,2 @@
+longpi = 3.141592653589793
+neglongpi = -3.141592653589793
diff --git a/tests/fixtures/valid/long-integer.json b/crates/test-suite/tests/valid/long-integer.json
similarity index 100%
rename from tests/fixtures/valid/long-integer.json
rename to crates/test-suite/tests/valid/long-integer.json
diff --git a/tests/fixtures/valid/long-integer.toml b/crates/test-suite/tests/valid/long-integer.toml
similarity index 100%
rename from tests/fixtures/valid/long-integer.toml
rename to crates/test-suite/tests/valid/long-integer.toml
diff --git a/crates/test-suite/tests/valid/multiline-string.json b/crates/test-suite/tests/valid/multiline-string.json
new file mode 100644
index 00000000..3223bae3
--- /dev/null
+++ b/crates/test-suite/tests/valid/multiline-string.json
@@ -0,0 +1,38 @@
+{
+    "multiline_empty_one": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline_empty_two": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline_empty_three": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline_empty_four": {
+        "type": "string",
+        "value": ""
+    },
+    "multiline_empty_five": {
+        "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."
+    },
+    "equivalent_four": {
+        "type": "string",
+        "value": "The quick brown fox jumps over the lazy dog."
+    }
+}
diff --git a/crates/test-suite/tests/valid/multiline-string.toml b/crates/test-suite/tests/valid/multiline-string.toml
new file mode 100644
index 00000000..2c4237fd
--- /dev/null
+++ b/crates/test-suite/tests/valid/multiline-string.toml
@@ -0,0 +1,34 @@
+multiline_empty_one = """"""
+multiline_empty_two = """
+"""
+multiline_empty_three = """\
+    """
+multiline_empty_four = """\
+   \
+   \
+   """
+multiline_empty_five = """\
+   \ 
+   \	
+   \ 	
+   """
+
+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.\
+       """
+
+equivalent_four = """\
+       The quick brown \ 
+       fox jumps over \				
+       the lazy dog.\	
+       """
diff --git a/crates/test-suite/tests/valid/quote-surrounded-value.json b/crates/test-suite/tests/valid/quote-surrounded-value.json
new file mode 100644
index 00000000..84495cf2
--- /dev/null
+++ b/crates/test-suite/tests/valid/quote-surrounded-value.json
@@ -0,0 +1,10 @@
+{
+  "double": {
+    "type": "string",
+    "value": "\"double quotes here\""
+  },
+  "single": {
+    "type": "string",
+    "value": "'single quotes here'"
+  }
+}
diff --git a/crates/test-suite/tests/valid/quote-surrounded-value.toml b/crates/test-suite/tests/valid/quote-surrounded-value.toml
new file mode 100644
index 00000000..dc8697e2
--- /dev/null
+++ b/crates/test-suite/tests/valid/quote-surrounded-value.toml
@@ -0,0 +1,2 @@
+double = '"double quotes here"'
+single = "'single quotes here'"
diff --git a/tests/fixtures/valid/raw-multiline-string.json b/crates/test-suite/tests/valid/raw-multiline-string.json
similarity index 100%
rename from tests/fixtures/valid/raw-multiline-string.json
rename to crates/test-suite/tests/valid/raw-multiline-string.json
diff --git a/tests/fixtures/valid/raw-multiline-string.toml b/crates/test-suite/tests/valid/raw-multiline-string.toml
similarity index 100%
rename from tests/fixtures/valid/raw-multiline-string.toml
rename to crates/test-suite/tests/valid/raw-multiline-string.toml
diff --git a/crates/test-suite/tests/valid/raw-string.json b/crates/test-suite/tests/valid/raw-string.json
new file mode 100644
index 00000000..693ab9b5
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/raw-string.toml b/crates/test-suite/tests/valid/raw-string.toml
new file mode 100644
index 00000000..92acd255
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/string-delim-end.json b/crates/test-suite/tests/valid/string-delim-end.json
new file mode 100644
index 00000000..69b5a0a8
--- /dev/null
+++ b/crates/test-suite/tests/valid/string-delim-end.json
@@ -0,0 +1,14 @@
+{
+  "str1": {
+    "type": "string",
+    "value": "\"This,\" she said, \"is just a pointless statement.\""
+  },
+  "str2": {
+    "type": "string",
+    "value": "foo''bar''"
+  },
+  "str3": {
+    "type": "string",
+    "value": "\"\""
+  }
+}
\ No newline at end of file
diff --git a/crates/test-suite/tests/valid/string-delim-end.toml b/crates/test-suite/tests/valid/string-delim-end.toml
new file mode 100644
index 00000000..9a412196
--- /dev/null
+++ b/crates/test-suite/tests/valid/string-delim-end.toml
@@ -0,0 +1,3 @@
+str1 = """"This," she said, "is just a pointless statement.""""
+str2 = '''foo''bar'''''
+str3 = """"""""
diff --git a/crates/test-suite/tests/valid/string-empty.json b/crates/test-suite/tests/valid/string-empty.json
new file mode 100644
index 00000000..6c26d695
--- /dev/null
+++ b/crates/test-suite/tests/valid/string-empty.json
@@ -0,0 +1,6 @@
+{
+    "answer": {
+        "type": "string",
+        "value": ""
+    }
+}
diff --git a/crates/test-suite/tests/valid/string-empty.toml b/crates/test-suite/tests/valid/string-empty.toml
new file mode 100644
index 00000000..e37e6815
--- /dev/null
+++ b/crates/test-suite/tests/valid/string-empty.toml
@@ -0,0 +1,1 @@
+answer = ""
diff --git a/crates/test-suite/tests/valid/string-escapes.json b/crates/test-suite/tests/valid/string-escapes.json
new file mode 100644
index 00000000..e3b2d94a
--- /dev/null
+++ b/crates/test-suite/tests/valid/string-escapes.json
@@ -0,0 +1,58 @@
+{
+    "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."
+    },
+    "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."
+    },
+    "delete": {
+        "type": "string",
+        "value": "This string has a \u007f delete control code."
+    },
+    "unitseparator": {
+        "type": "string",
+        "value": "This string has a \u001f unit separator control code."
+    }
+}
diff --git a/crates/test-suite/tests/valid/string-escapes.toml b/crates/test-suite/tests/valid/string-escapes.toml
new file mode 100644
index 00000000..152c6ad9
--- /dev/null
+++ b/crates/test-suite/tests/valid/string-escapes.toml
@@ -0,0 +1,14 @@
+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."
+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."
+delete = "This string has a \u007F delete control code."
+unitseparator = "This string has a \u001F unit separator control code."
diff --git a/crates/test-suite/tests/valid/string-simple.json b/crates/test-suite/tests/valid/string-simple.json
new file mode 100644
index 00000000..2e05f99b
--- /dev/null
+++ b/crates/test-suite/tests/valid/string-simple.json
@@ -0,0 +1,6 @@
+{
+    "answer": {
+        "type": "string",
+        "value": "You are not drinking enough whisky."
+    }
+}
diff --git a/crates/test-suite/tests/valid/string-simple.toml b/crates/test-suite/tests/valid/string-simple.toml
new file mode 100644
index 00000000..e17ade62
--- /dev/null
+++ b/crates/test-suite/tests/valid/string-simple.toml
@@ -0,0 +1,1 @@
+answer = "You are not drinking enough whisky."
diff --git a/crates/test-suite/tests/valid/string-with-pound.json b/crates/test-suite/tests/valid/string-with-pound.json
new file mode 100644
index 00000000..33cdc9c4
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/string-with-pound.toml b/crates/test-suite/tests/valid/string-with-pound.toml
new file mode 100644
index 00000000..5fd87466
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/table-array-implicit.json b/crates/test-suite/tests/valid/table-array-implicit.json
new file mode 100644
index 00000000..32e46401
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-array-implicit.json
@@ -0,0 +1,7 @@
+{
+    "albums": {
+       "songs": [
+           {"name": {"type": "string", "value": "Glory Days"}}
+       ]
+    }
+}
diff --git a/crates/test-suite/tests/valid/table-array-implicit.toml b/crates/test-suite/tests/valid/table-array-implicit.toml
new file mode 100644
index 00000000..3157ac98
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-array-implicit.toml
@@ -0,0 +1,2 @@
+[[albums.songs]]
+name = "Glory Days"
diff --git a/crates/test-suite/tests/valid/table-array-many.json b/crates/test-suite/tests/valid/table-array-many.json
new file mode 100644
index 00000000..84df2dab
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/table-array-many.toml b/crates/test-suite/tests/valid/table-array-many.toml
new file mode 100644
index 00000000..46062beb
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/table-array-nest-no-keys.json b/crates/test-suite/tests/valid/table-array-nest-no-keys.json
new file mode 100644
index 00000000..7537b1a1
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-array-nest-no-keys.json
@@ -0,0 +1,14 @@
+{
+    "albums": [
+        {
+            "songs": [{}, {}]
+        }
+    ],
+    "artists": [
+        {
+            "home": {
+                "address": {}
+            }
+        }
+    ]
+}
diff --git a/crates/test-suite/tests/valid/table-array-nest-no-keys.toml b/crates/test-suite/tests/valid/table-array-nest-no-keys.toml
new file mode 100644
index 00000000..ad6eb106
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-array-nest-no-keys.toml
@@ -0,0 +1,6 @@
+[[ albums ]]
+  [[ albums.songs ]]
+  [[ albums.songs ]]
+
+[[ artists ]]
+  [ artists.home.address ]
diff --git a/crates/test-suite/tests/valid/table-array-nest.json b/crates/test-suite/tests/valid/table-array-nest.json
new file mode 100644
index 00000000..c117afa4
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/table-array-nest.toml b/crates/test-suite/tests/valid/table-array-nest.toml
new file mode 100644
index 00000000..d659a3d9
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/table-array-one.json b/crates/test-suite/tests/valid/table-array-one.json
new file mode 100644
index 00000000..d75faaeb
--- /dev/null
+++ b/crates/test-suite/tests/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/crates/test-suite/tests/valid/table-array-one.toml b/crates/test-suite/tests/valid/table-array-one.toml
new file mode 100644
index 00000000..cd7e1b69
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-array-one.toml
@@ -0,0 +1,3 @@
+[[people]]
+first_name = "Bruce"
+last_name = "Springsteen"
diff --git a/crates/test-suite/tests/valid/table-empty.json b/crates/test-suite/tests/valid/table-empty.json
new file mode 100644
index 00000000..6f3873af
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-empty.json
@@ -0,0 +1,3 @@
+{
+    "a": {}
+}
diff --git a/crates/test-suite/tests/valid/table-empty.toml b/crates/test-suite/tests/valid/table-empty.toml
new file mode 100644
index 00000000..8bb6a0aa
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-empty.toml
@@ -0,0 +1,1 @@
+[a]
diff --git a/crates/test-suite/tests/valid/table-multi-empty.json b/crates/test-suite/tests/valid/table-multi-empty.json
new file mode 100644
index 00000000..a6e17c92
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-multi-empty.json
@@ -0,0 +1,5 @@
+{
+    "a": { "b": {} },
+    "b": {},
+    "c": { "a": {} }
+}
diff --git a/crates/test-suite/tests/valid/table-multi-empty.toml b/crates/test-suite/tests/valid/table-multi-empty.toml
new file mode 100644
index 00000000..2266ed2d
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-multi-empty.toml
@@ -0,0 +1,5 @@
+[a]
+[a.b]
+[b]
+[c]
+[c.a]
diff --git a/crates/test-suite/tests/valid/table-sub-empty.json b/crates/test-suite/tests/valid/table-sub-empty.json
new file mode 100644
index 00000000..97877708
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-sub-empty.json
@@ -0,0 +1,3 @@
+{
+    "a": { "b": {} }
+}
diff --git a/crates/test-suite/tests/valid/table-sub-empty.toml b/crates/test-suite/tests/valid/table-sub-empty.toml
new file mode 100644
index 00000000..70b7fe11
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-sub-empty.toml
@@ -0,0 +1,2 @@
+[a]
+[a.b]
diff --git a/crates/test-suite/tests/valid/table-whitespace.json b/crates/test-suite/tests/valid/table-whitespace.json
new file mode 100644
index 00000000..3a73ec86
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-whitespace.json
@@ -0,0 +1,3 @@
+{
+    "valid key": {}
+}
diff --git a/crates/test-suite/tests/valid/table-whitespace.toml b/crates/test-suite/tests/valid/table-whitespace.toml
new file mode 100644
index 00000000..daf881d1
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-whitespace.toml
@@ -0,0 +1,1 @@
+["valid key"]
diff --git a/crates/test-suite/tests/valid/table-with-pound.json b/crates/test-suite/tests/valid/table-with-pound.json
new file mode 100644
index 00000000..5e594e41
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-with-pound.json
@@ -0,0 +1,5 @@
+{
+    "key#group": {
+        "answer": {"type": "integer", "value": "42"}
+    }
+}
diff --git a/crates/test-suite/tests/valid/table-with-pound.toml b/crates/test-suite/tests/valid/table-with-pound.toml
new file mode 100644
index 00000000..33f2c4fd
--- /dev/null
+++ b/crates/test-suite/tests/valid/table-with-pound.toml
@@ -0,0 +1,2 @@
+["key#group"]
+answer = 42
diff --git a/crates/test-suite/tests/valid/unicode-escape.json b/crates/test-suite/tests/valid/unicode-escape.json
new file mode 100644
index 00000000..06fae705
--- /dev/null
+++ b/crates/test-suite/tests/valid/unicode-escape.json
@@ -0,0 +1,8 @@
+{
+    "answer1": {"type": "string", "value": "\u000B"},
+    "answer4": {"type": "string", "value": "\u03B4α"},
+    "answer8": {"type": "string", "value": "\u03B4β"},
+    "answer9": {"type": "string", "value": "\uc0de"},
+    "answer10": {"type": "string", "value": "\u03B4α"},
+    "answer11": {"type": "string", "value": "\uABC1"}
+}
diff --git a/crates/test-suite/tests/valid/unicode-escape.toml b/crates/test-suite/tests/valid/unicode-escape.toml
new file mode 100644
index 00000000..6654252a
--- /dev/null
+++ b/crates/test-suite/tests/valid/unicode-escape.toml
@@ -0,0 +1,6 @@
+answer1 = "\u000B"
+answer4 = "\u03B4α"
+answer8 = "\U000003B4β"
+answer9 = "\uc0de"
+answer10 = "\u03b4α"
+answer11 = "\U0000abc1"
diff --git a/crates/test-suite/tests/valid/unicode-literal.json b/crates/test-suite/tests/valid/unicode-literal.json
new file mode 100644
index 00000000..00aa2f83
--- /dev/null
+++ b/crates/test-suite/tests/valid/unicode-literal.json
@@ -0,0 +1,3 @@
+{
+    "answer": {"type": "string", "value": "δ"}
+}
diff --git a/crates/test-suite/tests/valid/unicode-literal.toml b/crates/test-suite/tests/valid/unicode-literal.toml
new file mode 100644
index 00000000..c65723ca
--- /dev/null
+++ b/crates/test-suite/tests/valid/unicode-literal.toml
@@ -0,0 +1,1 @@
+answer = "δ"
diff --git a/crates/toml/tests/enum_external_deserialize.rs b/crates/toml/tests/enum_external_deserialize.rs
new file mode 100644
index 00000000..6d45b484
--- /dev/null
+++ b/crates/toml/tests/enum_external_deserialize.rs
@@ -0,0 +1,258 @@
+#[macro_use]
+extern crate serde_derive;
+extern crate toml;
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct OuterStruct {
+    inner: TheEnum,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+enum TheEnum {
+    Plain,
+    Tuple(i64, bool),
+    NewType(String),
+    Struct { value: i64 },
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct Val {
+    val: TheEnum,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+struct Multi {
+    enums: Vec<TheEnum>,
+}
+
+#[test]
+fn invalid_variant_returns_error_with_good_message_string() {
+    let error = toml::from_str::<TheEnum>("\"NonExistent\"").unwrap_err();
+
+    assert_eq!(
+        error.to_string(),
+        "unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`"
+    );
+}
+
+#[test]
+fn invalid_variant_returns_error_with_good_message_inline_table() {
+    let error = toml::from_str::<TheEnum>("{ NonExistent = {} }").unwrap_err();
+    assert_eq!(
+        error.to_string(),
+        "unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`"
+    );
+}
+
+#[test]
+fn extra_field_returns_expected_empty_table_error() {
+    let error = toml::from_str::<TheEnum>("{ Plain = { extra_field = 404 } }").unwrap_err();
+
+    assert_eq!(error.to_string(), "expected empty table");
+}
+
+#[test]
+fn extra_field_returns_expected_empty_table_error_struct_variant() {
+    let error = toml::from_str::<TheEnum>("{ Struct = { value = 123, extra_0 = 0, extra_1 = 1 } }")
+        .unwrap_err();
+
+    assert_eq!(
+        error.to_string(),
+        r#"unexpected keys in table: `["extra_0", "extra_1"]`, available keys: `["value"]`"#
+    );
+}
+
+mod enum_unit {
+    use super::*;
+
+    #[test]
+    fn from_str() {
+        assert_eq!(TheEnum::Plain, toml::from_str("\"Plain\"").unwrap());
+    }
+
+    #[test]
+    fn from_inline_table() {
+        assert_eq!(TheEnum::Plain, toml::from_str("{ Plain = {} }").unwrap());
+        assert_eq!(
+            Val {
+                val: TheEnum::Plain
+            },
+            toml::from_str("val = { Plain = {} }").unwrap()
+        );
+    }
+
+    #[test]
+    fn from_dotted_table() {
+        assert_eq!(TheEnum::Plain, toml::from_str("[Plain]\n").unwrap());
+    }
+}
+
+mod enum_tuple {
+    use super::*;
+
+    #[test]
+    fn from_inline_table() {
+        assert_eq!(
+            TheEnum::Tuple(-123, true),
+            toml::from_str("{ Tuple = { 0 = -123, 1 = true } }").unwrap()
+        );
+        assert_eq!(
+            Val {
+                val: TheEnum::Tuple(-123, true)
+            },
+            toml::from_str("val = { Tuple = { 0 = -123, 1 = true } }").unwrap()
+        );
+    }
+
+    #[test]
+    fn from_dotted_table() {
+        assert_eq!(
+            TheEnum::Tuple(-123, true),
+            toml::from_str(
+                r#"[Tuple]
+                0 = -123
+                1 = true
+                "#
+            )
+            .unwrap()
+        );
+    }
+}
+
+mod enum_newtype {
+    use super::*;
+
+    #[test]
+    fn from_inline_table() {
+        assert_eq!(
+            TheEnum::NewType("value".to_string()),
+            toml::from_str(r#"{ NewType = "value" }"#).unwrap()
+        );
+        assert_eq!(
+            Val {
+                val: TheEnum::NewType("value".to_string()),
+            },
+            toml::from_str(r#"val = { NewType = "value" }"#).unwrap()
+        );
+    }
+
+    #[test]
+    #[ignore = "Unimplemented: https://github.com/alexcrichton/toml-rs/pull/264#issuecomment-431707209"]
+    fn from_dotted_table() {
+        assert_eq!(
+            TheEnum::NewType("value".to_string()),
+            toml::from_str(r#"NewType = "value""#).unwrap()
+        );
+        assert_eq!(
+            Val {
+                val: TheEnum::NewType("value".to_string()),
+            },
+            toml::from_str(
+                r#"[val]
+                NewType = "value"
+                "#
+            )
+            .unwrap()
+        );
+    }
+}
+
+mod enum_struct {
+    use super::*;
+
+    #[test]
+    fn from_inline_table() {
+        assert_eq!(
+            TheEnum::Struct { value: -123 },
+            toml::from_str("{ Struct = { value = -123 } }").unwrap()
+        );
+        assert_eq!(
+            Val {
+                val: TheEnum::Struct { value: -123 }
+            },
+            toml::from_str("val = { Struct = { value = -123 } }").unwrap()
+        );
+    }
+
+    #[test]
+    fn from_dotted_table() {
+        assert_eq!(
+            TheEnum::Struct { value: -123 },
+            toml::from_str(
+                r#"[Struct]
+                value = -123
+                "#
+            )
+            .unwrap()
+        );
+    }
+
+    #[test]
+    fn from_nested_dotted_table() {
+        assert_eq!(
+            OuterStruct {
+                inner: TheEnum::Struct { value: -123 }
+            },
+            toml::from_str(
+                r#"[inner.Struct]
+                value = -123
+                "#
+            )
+            .unwrap()
+        );
+    }
+}
+
+mod enum_array {
+    use super::*;
+
+    #[test]
+    fn from_inline_tables() {
+        let toml_str = r#"
+            enums = [
+                { Plain = {} },
+                { Tuple = { 0 = -123, 1 = true } },
+                { NewType = "value" },
+                { Struct = { value = -123 } }
+            ]"#;
+        assert_eq!(
+            Multi {
+                enums: vec![
+                    TheEnum::Plain,
+                    TheEnum::Tuple(-123, true),
+                    TheEnum::NewType("value".to_string()),
+                    TheEnum::Struct { value: -123 },
+                ]
+            },
+            toml::from_str(toml_str).unwrap()
+        );
+    }
+
+    #[test]
+    #[ignore = "Unimplemented: https://github.com/alexcrichton/toml-rs/pull/264#issuecomment-431707209"]
+    fn from_dotted_table() {
+        let toml_str = r#"[[enums]]
+            Plain = {}
+
+            [[enums]]
+            Tuple = { 0 = -123, 1 = true }
+
+            [[enums]]
+            NewType = "value"
+
+            [[enums]]
+            Struct = { value = -123 }
+            "#;
+        assert_eq!(
+            Multi {
+                enums: vec![
+                    TheEnum::Plain,
+                    TheEnum::Tuple(-123, true),
+                    TheEnum::NewType("value".to_string()),
+                    TheEnum::Struct { value: -123 },
+                ]
+            },
+            toml::from_str(toml_str).unwrap()
+        );
+    }
+}
diff --git a/tests/convert.rs b/crates/toml_edit/tests/convert.rs
similarity index 100%
rename from tests/convert.rs
rename to crates/toml_edit/tests/convert.rs
diff --git a/tests/decoder.rs b/crates/toml_edit/tests/decoder.rs
similarity index 100%
rename from tests/decoder.rs
rename to crates/toml_edit/tests/decoder.rs
diff --git a/tests/decoder_compliance.rs b/crates/toml_edit/tests/decoder_compliance.rs
similarity index 100%
rename from tests/decoder_compliance.rs
rename to crates/toml_edit/tests/decoder_compliance.rs
diff --git a/tests/easy_decoder.rs b/crates/toml_edit/tests/easy_decoder.rs
similarity index 100%
rename from tests/easy_decoder.rs
rename to crates/toml_edit/tests/easy_decoder.rs
diff --git a/tests/easy_decoder_compliance.rs b/crates/toml_edit/tests/easy_decoder_compliance.rs
similarity index 100%
rename from tests/easy_decoder_compliance.rs
rename to crates/toml_edit/tests/easy_decoder_compliance.rs
diff --git a/tests/easy_encoder.rs b/crates/toml_edit/tests/easy_encoder.rs
similarity index 100%
rename from tests/easy_encoder.rs
rename to crates/toml_edit/tests/easy_encoder.rs
diff --git a/tests/easy_encoder_compliance.rs b/crates/toml_edit/tests/easy_encoder_compliance.rs
similarity index 100%
rename from tests/easy_encoder_compliance.rs
rename to crates/toml_edit/tests/easy_encoder_compliance.rs
diff --git a/tests/encoder.rs b/crates/toml_edit/tests/encoder.rs
similarity index 100%
rename from tests/encoder.rs
rename to crates/toml_edit/tests/encoder.rs
diff --git a/tests/encoder_compliance.rs b/crates/toml_edit/tests/encoder_compliance.rs
similarity index 100%
rename from tests/encoder_compliance.rs
rename to crates/toml_edit/tests/encoder_compliance.rs
diff --git a/tests/enum_external_deserialize.rs b/crates/toml_edit/tests/enum_external_deserialize.rs
similarity index 100%
rename from tests/enum_external_deserialize.rs
rename to crates/toml_edit/tests/enum_external_deserialize.rs
diff --git a/tests/fixtures/README.md b/crates/toml_edit/tests/fixtures/README.md
similarity index 100%
rename from tests/fixtures/README.md
rename to crates/toml_edit/tests/fixtures/README.md
diff --git a/tests/fixtures/invalid/datetime-malformed-no-leads.stderr b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.stderr
similarity index 100%
rename from tests/fixtures/invalid/datetime-malformed-no-leads.stderr
rename to crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.toml b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.toml
new file mode 100644
index 00000000..123f173b
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-leads.toml
@@ -0,0 +1,1 @@
+no-leads = 1987-7-05T17:45:00Z
diff --git a/tests/fixtures/invalid/datetime-malformed-no-secs.stderr b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.stderr
similarity index 100%
rename from tests/fixtures/invalid/datetime-malformed-no-secs.stderr
rename to crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.toml b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.toml
new file mode 100644
index 00000000..ba939007
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-secs.toml
@@ -0,0 +1,1 @@
+no-secs = 1987-07-05T17:45Z
diff --git a/tests/fixtures/invalid/datetime-malformed-no-t.stderr b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.stderr
similarity index 100%
rename from tests/fixtures/invalid/datetime-malformed-no-t.stderr
rename to crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.toml b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.toml
new file mode 100644
index 00000000..617e3c56
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-no-t.toml
@@ -0,0 +1,1 @@
+no-t = 1987-07-0517:45:00Z
diff --git a/tests/fixtures/invalid/datetime-malformed-with-milli.stderr b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.stderr
similarity index 100%
rename from tests/fixtures/invalid/datetime-malformed-with-milli.stderr
rename to crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.toml b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.toml
new file mode 100644
index 00000000..eef792f3
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/datetime-malformed-with-milli.toml
@@ -0,0 +1,1 @@
+with-milli = 1987-07-5T17:45:00.12Z
diff --git a/tests/fixtures/invalid/duplicate-key-dotted-into-std.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-dotted-into-std.stderr
similarity index 100%
rename from tests/fixtures/invalid/duplicate-key-dotted-into-std.stderr
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-key-dotted-into-std.stderr
diff --git a/tests/fixtures/invalid/duplicate-key-dotted-into-std.toml b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-dotted-into-std.toml
similarity index 100%
rename from tests/fixtures/invalid/duplicate-key-dotted-into-std.toml
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-key-dotted-into-std.toml
diff --git a/tests/fixtures/invalid/duplicate-key-std-into-dotted.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-std-into-dotted.stderr
similarity index 100%
rename from tests/fixtures/invalid/duplicate-key-std-into-dotted.stderr
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-key-std-into-dotted.stderr
diff --git a/tests/fixtures/invalid/duplicate-key-std-into-dotted.toml b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-std-into-dotted.toml
similarity index 100%
rename from tests/fixtures/invalid/duplicate-key-std-into-dotted.toml
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-key-std-into-dotted.toml
diff --git a/tests/fixtures/invalid/duplicate-key-table.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.stderr
similarity index 100%
rename from tests/fixtures/invalid/duplicate-key-table.stderr
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.toml b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.toml
new file mode 100644
index 00000000..cedf05fc
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-key-table.toml
@@ -0,0 +1,5 @@
+[fruit]
+type = "apple"
+
+[fruit.type]
+apple = "yes"
diff --git a/tests/fixtures/invalid/duplicate-keys-cargo.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-cargo.stderr
similarity index 100%
rename from tests/fixtures/invalid/duplicate-keys-cargo.stderr
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-keys-cargo.stderr
diff --git a/tests/fixtures/invalid/duplicate-keys-cargo.toml b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-cargo.toml
similarity index 100%
rename from tests/fixtures/invalid/duplicate-keys-cargo.toml
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-keys-cargo.toml
diff --git a/tests/fixtures/invalid/duplicate-keys-dotted.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-dotted.stderr
similarity index 100%
rename from tests/fixtures/invalid/duplicate-keys-dotted.stderr
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-keys-dotted.stderr
diff --git a/tests/fixtures/invalid/duplicate-keys-dotted.toml b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys-dotted.toml
similarity index 100%
rename from tests/fixtures/invalid/duplicate-keys-dotted.toml
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-keys-dotted.toml
diff --git a/tests/fixtures/invalid/duplicate-keys.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys.stderr
similarity index 100%
rename from tests/fixtures/invalid/duplicate-keys.stderr
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-keys.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-keys.toml b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys.toml
new file mode 100644
index 00000000..9b5aee0e
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-keys.toml
@@ -0,0 +1,2 @@
+dupe = false
+dupe = true
diff --git a/tests/fixtures/invalid/duplicate-tables.stderr b/crates/toml_edit/tests/fixtures/invalid/duplicate-tables.stderr
similarity index 100%
rename from tests/fixtures/invalid/duplicate-tables.stderr
rename to crates/toml_edit/tests/fixtures/invalid/duplicate-tables.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/duplicate-tables.toml b/crates/toml_edit/tests/fixtures/invalid/duplicate-tables.toml
new file mode 100644
index 00000000..8ddf49b4
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/duplicate-tables.toml
@@ -0,0 +1,2 @@
+[a]
+[a]
diff --git a/tests/fixtures/invalid/empty-implicit-table.stderr b/crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.stderr
similarity index 100%
rename from tests/fixtures/invalid/empty-implicit-table.stderr
rename to crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.toml b/crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.toml
new file mode 100644
index 00000000..0cc36d0d
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/empty-implicit-table.toml
@@ -0,0 +1,1 @@
+[naughty..naughty]
diff --git a/tests/fixtures/invalid/empty-table.stderr b/crates/toml_edit/tests/fixtures/invalid/empty-table.stderr
similarity index 100%
rename from tests/fixtures/invalid/empty-table.stderr
rename to crates/toml_edit/tests/fixtures/invalid/empty-table.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/empty-table.toml b/crates/toml_edit/tests/fixtures/invalid/empty-table.toml
new file mode 100644
index 00000000..fe51488c
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/empty-table.toml
@@ -0,0 +1,1 @@
+[]
diff --git a/tests/fixtures/invalid/float-leading-zero-neg.stderr b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-neg.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-leading-zero-neg.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-leading-zero-neg.stderr
diff --git a/tests/fixtures/invalid/float-leading-zero-neg.toml b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-neg.toml
similarity index 100%
rename from tests/fixtures/invalid/float-leading-zero-neg.toml
rename to crates/toml_edit/tests/fixtures/invalid/float-leading-zero-neg.toml
diff --git a/tests/fixtures/invalid/float-leading-zero-pos.stderr b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-pos.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-leading-zero-pos.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-leading-zero-pos.stderr
diff --git a/tests/fixtures/invalid/float-leading-zero-pos.toml b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero-pos.toml
similarity index 100%
rename from tests/fixtures/invalid/float-leading-zero-pos.toml
rename to crates/toml_edit/tests/fixtures/invalid/float-leading-zero-pos.toml
diff --git a/tests/fixtures/invalid/float-leading-zero.stderr b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-leading-zero.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-leading-zero.stderr
diff --git a/tests/fixtures/invalid/float-leading-zero.toml b/crates/toml_edit/tests/fixtures/invalid/float-leading-zero.toml
similarity index 100%
rename from tests/fixtures/invalid/float-leading-zero.toml
rename to crates/toml_edit/tests/fixtures/invalid/float-leading-zero.toml
diff --git a/tests/fixtures/invalid/float-no-leading-zero.stderr b/crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-no-leading-zero.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.toml b/crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.toml
new file mode 100644
index 00000000..cab76bfd
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/float-no-leading-zero.toml
@@ -0,0 +1,2 @@
+answer = .12345
+neganswer = -.12345
diff --git a/tests/fixtures/invalid/float-no-trailing-digits.stderr b/crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-no-trailing-digits.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.toml b/crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.toml
new file mode 100644
index 00000000..cbff2d06
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/float-no-trailing-digits.toml
@@ -0,0 +1,2 @@
+answer = 1.
+neganswer = -1.
diff --git a/tests/fixtures/invalid/float-underscore-after-point.stderr b/crates/toml_edit/tests/fixtures/invalid/float-underscore-after-point.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-underscore-after-point.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-underscore-after-point.stderr
diff --git a/tests/fixtures/invalid/float-underscore-after-point.toml b/crates/toml_edit/tests/fixtures/invalid/float-underscore-after-point.toml
similarity index 100%
rename from tests/fixtures/invalid/float-underscore-after-point.toml
rename to crates/toml_edit/tests/fixtures/invalid/float-underscore-after-point.toml
diff --git a/tests/fixtures/invalid/float-underscore-after.stderr b/crates/toml_edit/tests/fixtures/invalid/float-underscore-after.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-underscore-after.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-underscore-after.stderr
diff --git a/tests/fixtures/invalid/float-underscore-after.toml b/crates/toml_edit/tests/fixtures/invalid/float-underscore-after.toml
similarity index 100%
rename from tests/fixtures/invalid/float-underscore-after.toml
rename to crates/toml_edit/tests/fixtures/invalid/float-underscore-after.toml
diff --git a/tests/fixtures/invalid/float-underscore-before-point.stderr b/crates/toml_edit/tests/fixtures/invalid/float-underscore-before-point.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-underscore-before-point.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-underscore-before-point.stderr
diff --git a/tests/fixtures/invalid/float-underscore-before-point.toml b/crates/toml_edit/tests/fixtures/invalid/float-underscore-before-point.toml
similarity index 100%
rename from tests/fixtures/invalid/float-underscore-before-point.toml
rename to crates/toml_edit/tests/fixtures/invalid/float-underscore-before-point.toml
diff --git a/tests/fixtures/invalid/float-underscore-before.stderr b/crates/toml_edit/tests/fixtures/invalid/float-underscore-before.stderr
similarity index 100%
rename from tests/fixtures/invalid/float-underscore-before.stderr
rename to crates/toml_edit/tests/fixtures/invalid/float-underscore-before.stderr
diff --git a/tests/fixtures/invalid/float-underscore-before.toml b/crates/toml_edit/tests/fixtures/invalid/float-underscore-before.toml
similarity index 100%
rename from tests/fixtures/invalid/float-underscore-before.toml
rename to crates/toml_edit/tests/fixtures/invalid/float-underscore-before.toml
diff --git a/tests/fixtures/invalid/inline-table-newline.stderr b/crates/toml_edit/tests/fixtures/invalid/inline-table-newline.stderr
similarity index 100%
rename from tests/fixtures/invalid/inline-table-newline.stderr
rename to crates/toml_edit/tests/fixtures/invalid/inline-table-newline.stderr
diff --git a/tests/fixtures/invalid/inline-table-newline.toml b/crates/toml_edit/tests/fixtures/invalid/inline-table-newline.toml
similarity index 100%
rename from tests/fixtures/invalid/inline-table-newline.toml
rename to crates/toml_edit/tests/fixtures/invalid/inline-table-newline.toml
diff --git a/tests/fixtures/invalid/integer-invalid-binary-char.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-binary-char.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-invalid-binary-char.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-invalid-binary-char.stderr
diff --git a/tests/fixtures/invalid/integer-invalid-binary-char.toml b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-binary-char.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-invalid-binary-char.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-invalid-binary-char.toml
diff --git a/tests/fixtures/invalid/integer-invalid-hex-char.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-hex-char.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-invalid-hex-char.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-invalid-hex-char.stderr
diff --git a/tests/fixtures/invalid/integer-invalid-hex-char.toml b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-hex-char.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-invalid-hex-char.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-invalid-hex-char.toml
diff --git a/tests/fixtures/invalid/integer-invalid-octal-char.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-octal-char.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-invalid-octal-char.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-invalid-octal-char.stderr
diff --git a/tests/fixtures/invalid/integer-invalid-octal-char.toml b/crates/toml_edit/tests/fixtures/invalid/integer-invalid-octal-char.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-invalid-octal-char.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-invalid-octal-char.toml
diff --git a/tests/fixtures/invalid/integer-leading-zero-neg.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-neg.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-leading-zero-neg.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-neg.stderr
diff --git a/tests/fixtures/invalid/integer-leading-zero-neg.toml b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-neg.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-leading-zero-neg.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-neg.toml
diff --git a/tests/fixtures/invalid/integer-leading-zero-pos.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-pos.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-leading-zero-pos.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-pos.stderr
diff --git a/tests/fixtures/invalid/integer-leading-zero-pos.toml b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-pos.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-leading-zero-pos.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-leading-zero-pos.toml
diff --git a/tests/fixtures/invalid/integer-leading-zero.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-leading-zero.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-leading-zero.stderr
diff --git a/tests/fixtures/invalid/integer-leading-zero.toml b/crates/toml_edit/tests/fixtures/invalid/integer-leading-zero.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-leading-zero.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-leading-zero.toml
diff --git a/tests/fixtures/invalid/integer-underscore-after.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-after.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-underscore-after.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-underscore-after.stderr
diff --git a/tests/fixtures/invalid/integer-underscore-after.toml b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-after.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-underscore-after.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-underscore-after.toml
diff --git a/tests/fixtures/invalid/integer-underscore-before.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-before.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-underscore-before.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-underscore-before.stderr
diff --git a/tests/fixtures/invalid/integer-underscore-before.toml b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-before.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-underscore-before.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-underscore-before.toml
diff --git a/tests/fixtures/invalid/integer-underscore-double.stderr b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-double.stderr
similarity index 100%
rename from tests/fixtures/invalid/integer-underscore-double.stderr
rename to crates/toml_edit/tests/fixtures/invalid/integer-underscore-double.stderr
diff --git a/tests/fixtures/invalid/integer-underscore-double.toml b/crates/toml_edit/tests/fixtures/invalid/integer-underscore-double.toml
similarity index 100%
rename from tests/fixtures/invalid/integer-underscore-double.toml
rename to crates/toml_edit/tests/fixtures/invalid/integer-underscore-double.toml
diff --git a/tests/fixtures/invalid/key-after-array.stderr b/crates/toml_edit/tests/fixtures/invalid/key-after-array.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-after-array.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-after-array.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-after-array.toml b/crates/toml_edit/tests/fixtures/invalid/key-after-array.toml
new file mode 100644
index 00000000..5c1a1b0a
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-after-array.toml
@@ -0,0 +1,1 @@
+[[agencies]] owner = "S Cjelli"
diff --git a/tests/fixtures/invalid/key-after-table.stderr b/crates/toml_edit/tests/fixtures/invalid/key-after-table.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-after-table.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-after-table.stderr
diff --git a/tests/fixtures/invalid/key-after-table.toml b/crates/toml_edit/tests/fixtures/invalid/key-after-table.toml
similarity index 100%
rename from tests/fixtures/invalid/key-after-table.toml
rename to crates/toml_edit/tests/fixtures/invalid/key-after-table.toml
diff --git a/tests/fixtures/invalid/key-empty.stderr b/crates/toml_edit/tests/fixtures/invalid/key-empty.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-empty.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-empty.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-empty.toml b/crates/toml_edit/tests/fixtures/invalid/key-empty.toml
new file mode 100644
index 00000000..09f998f4
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-empty.toml
@@ -0,0 +1,1 @@
+ = 1
diff --git a/tests/fixtures/invalid/key-hash.stderr b/crates/toml_edit/tests/fixtures/invalid/key-hash.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-hash.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-hash.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-hash.toml b/crates/toml_edit/tests/fixtures/invalid/key-hash.toml
new file mode 100644
index 00000000..e321b1fb
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-hash.toml
@@ -0,0 +1,1 @@
+a# = 1
diff --git a/tests/fixtures/invalid/key-newline.stderr b/crates/toml_edit/tests/fixtures/invalid/key-newline.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-newline.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-newline.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-newline.toml b/crates/toml_edit/tests/fixtures/invalid/key-newline.toml
new file mode 100644
index 00000000..707aad54
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-newline.toml
@@ -0,0 +1,2 @@
+a
+= 1
diff --git a/tests/fixtures/invalid/key-no-eol.stderr b/crates/toml_edit/tests/fixtures/invalid/key-no-eol.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-no-eol.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-no-eol.stderr
diff --git a/tests/fixtures/invalid/key-no-eol.toml b/crates/toml_edit/tests/fixtures/invalid/key-no-eol.toml
similarity index 100%
rename from tests/fixtures/invalid/key-no-eol.toml
rename to crates/toml_edit/tests/fixtures/invalid/key-no-eol.toml
diff --git a/tests/fixtures/invalid/key-open-bracket.stderr b/crates/toml_edit/tests/fixtures/invalid/key-open-bracket.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-open-bracket.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-open-bracket.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-open-bracket.toml b/crates/toml_edit/tests/fixtures/invalid/key-open-bracket.toml
new file mode 100644
index 00000000..f0aeb16e
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-open-bracket.toml
@@ -0,0 +1,1 @@
+[abc = 1
diff --git a/tests/fixtures/invalid/key-single-open-bracket.stderr b/crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-single-open-bracket.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.toml b/crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.toml
new file mode 100644
index 00000000..8e2f0bef
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-single-open-bracket.toml
@@ -0,0 +1,1 @@
+[
\ No newline at end of file
diff --git a/tests/fixtures/invalid/key-space.stderr b/crates/toml_edit/tests/fixtures/invalid/key-space.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-space.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-space.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-space.toml b/crates/toml_edit/tests/fixtures/invalid/key-space.toml
new file mode 100644
index 00000000..201806d2
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-space.toml
@@ -0,0 +1,1 @@
+a b = 1
\ No newline at end of file
diff --git a/tests/fixtures/invalid/key-start-bracket.stderr b/crates/toml_edit/tests/fixtures/invalid/key-start-bracket.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-start-bracket.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-start-bracket.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-start-bracket.toml b/crates/toml_edit/tests/fixtures/invalid/key-start-bracket.toml
new file mode 100644
index 00000000..e0597ae1
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-start-bracket.toml
@@ -0,0 +1,3 @@
+[a]
+[xyz = 5
+[b]
diff --git a/tests/fixtures/invalid/key-two-equals.stderr b/crates/toml_edit/tests/fixtures/invalid/key-two-equals.stderr
similarity index 100%
rename from tests/fixtures/invalid/key-two-equals.stderr
rename to crates/toml_edit/tests/fixtures/invalid/key-two-equals.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/key-two-equals.toml b/crates/toml_edit/tests/fixtures/invalid/key-two-equals.toml
new file mode 100644
index 00000000..25a03789
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/key-two-equals.toml
@@ -0,0 +1,1 @@
+key= = 1
diff --git a/tests/fixtures/invalid/llbrace.stderr b/crates/toml_edit/tests/fixtures/invalid/llbrace.stderr
similarity index 100%
rename from tests/fixtures/invalid/llbrace.stderr
rename to crates/toml_edit/tests/fixtures/invalid/llbrace.stderr
diff --git a/tests/fixtures/invalid/llbrace.toml b/crates/toml_edit/tests/fixtures/invalid/llbrace.toml
similarity index 100%
rename from tests/fixtures/invalid/llbrace.toml
rename to crates/toml_edit/tests/fixtures/invalid/llbrace.toml
diff --git a/tests/fixtures/invalid/rrbrace.stderr b/crates/toml_edit/tests/fixtures/invalid/rrbrace.stderr
similarity index 100%
rename from tests/fixtures/invalid/rrbrace.stderr
rename to crates/toml_edit/tests/fixtures/invalid/rrbrace.stderr
diff --git a/tests/fixtures/invalid/rrbrace.toml b/crates/toml_edit/tests/fixtures/invalid/rrbrace.toml
similarity index 100%
rename from tests/fixtures/invalid/rrbrace.toml
rename to crates/toml_edit/tests/fixtures/invalid/rrbrace.toml
diff --git a/tests/fixtures/invalid/string-bad-byte-escape.stderr b/crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-bad-byte-escape.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.toml b/crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.toml
new file mode 100644
index 00000000..4c7be59f
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/string-bad-byte-escape.toml
@@ -0,0 +1,1 @@
+naughty = "\xAg"
diff --git a/tests/fixtures/invalid/string-bad-escape.stderr b/crates/toml_edit/tests/fixtures/invalid/string-bad-escape.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-bad-escape.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-bad-escape.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/string-bad-escape.toml b/crates/toml_edit/tests/fixtures/invalid/string-bad-escape.toml
new file mode 100644
index 00000000..60acb0cc
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/string-bad-escape.toml
@@ -0,0 +1,1 @@
+invalid-escape = "This string has a bad \a escape character."
diff --git a/tests/fixtures/invalid/string-bad-surrogate.stderr b/crates/toml_edit/tests/fixtures/invalid/string-bad-surrogate.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-bad-surrogate.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-bad-surrogate.stderr
diff --git a/tests/fixtures/invalid/string-bad-surrogate.toml b/crates/toml_edit/tests/fixtures/invalid/string-bad-surrogate.toml
similarity index 100%
rename from tests/fixtures/invalid/string-bad-surrogate.toml
rename to crates/toml_edit/tests/fixtures/invalid/string-bad-surrogate.toml
diff --git a/tests/fixtures/invalid/string-bad-uni-esc.stderr b/crates/toml_edit/tests/fixtures/invalid/string-bad-uni-esc.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-bad-uni-esc.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-bad-uni-esc.stderr
diff --git a/tests/fixtures/invalid/string-bad-uni-esc.toml b/crates/toml_edit/tests/fixtures/invalid/string-bad-uni-esc.toml
similarity index 100%
rename from tests/fixtures/invalid/string-bad-uni-esc.toml
rename to crates/toml_edit/tests/fixtures/invalid/string-bad-uni-esc.toml
diff --git a/tests/fixtures/invalid/string-byte-escapes.stderr b/crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-byte-escapes.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.toml b/crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.toml
new file mode 100644
index 00000000..e94452a8
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/string-byte-escapes.toml
@@ -0,0 +1,1 @@
+answer = "\x33"
diff --git a/tests/fixtures/invalid/string-no-close.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-close.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-no-close.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-no-close.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/string-no-close.toml b/crates/toml_edit/tests/fixtures/invalid/string-no-close.toml
new file mode 100644
index 00000000..0c292fca
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/string-no-close.toml
@@ -0,0 +1,1 @@
+no-ending-quote = "One time, at band camp
diff --git a/tests/fixtures/invalid/string-no-quotes-constant-like.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-constant-like.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-constant-like.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-constant-like.stderr
diff --git a/tests/fixtures/invalid/string-no-quotes-constant-like.toml b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-constant-like.toml
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-constant-like.toml
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-constant-like.toml
diff --git a/tests/fixtures/invalid/string-no-quotes-in-array.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-array.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-in-array.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-array.stderr
diff --git a/tests/fixtures/invalid/string-no-quotes-in-array.toml b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-array.toml
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-in-array.toml
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-array.toml
diff --git a/tests/fixtures/invalid/string-no-quotes-in-inline-table.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-inline-table.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-in-inline-table.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-inline-table.stderr
diff --git a/tests/fixtures/invalid/string-no-quotes-in-inline-table.toml b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-inline-table.toml
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-in-inline-table.toml
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-inline-table.toml
diff --git a/tests/fixtures/invalid/string-no-quotes-in-table.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-table.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-in-table.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-table.stderr
diff --git a/tests/fixtures/invalid/string-no-quotes-in-table.toml b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-table.toml
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-in-table.toml
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-in-table.toml
diff --git a/tests/fixtures/invalid/string-no-quotes-number-like.stderr b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-number-like.stderr
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-number-like.stderr
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-number-like.stderr
diff --git a/tests/fixtures/invalid/string-no-quotes-number-like.toml b/crates/toml_edit/tests/fixtures/invalid/string-no-quotes-number-like.toml
similarity index 100%
rename from tests/fixtures/invalid/string-no-quotes-number-like.toml
rename to crates/toml_edit/tests/fixtures/invalid/string-no-quotes-number-like.toml
diff --git a/tests/fixtures/invalid/table-array-implicit.stderr b/crates/toml_edit/tests/fixtures/invalid/table-array-implicit.stderr
similarity index 100%
rename from tests/fixtures/invalid/table-array-implicit.stderr
rename to crates/toml_edit/tests/fixtures/invalid/table-array-implicit.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/table-array-implicit.toml b/crates/toml_edit/tests/fixtures/invalid/table-array-implicit.toml
new file mode 100644
index 00000000..05f2507e
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/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/tests/fixtures/invalid/table-array-malformed-bracket.stderr b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.stderr
similarity index 100%
rename from tests/fixtures/invalid/table-array-malformed-bracket.stderr
rename to crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.toml b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.toml
new file mode 100644
index 00000000..39c73b05
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-bracket.toml
@@ -0,0 +1,2 @@
+[[albums]
+name = "Born to Run"
diff --git a/tests/fixtures/invalid/table-array-malformed-empty.stderr b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.stderr
similarity index 100%
rename from tests/fixtures/invalid/table-array-malformed-empty.stderr
rename to crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.toml b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.toml
new file mode 100644
index 00000000..a470ca33
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/table-array-malformed-empty.toml
@@ -0,0 +1,2 @@
+[[]]
+name = "Born to Run"
diff --git a/tests/fixtures/invalid/table-empty.stderr b/crates/toml_edit/tests/fixtures/invalid/table-empty.stderr
similarity index 100%
rename from tests/fixtures/invalid/table-empty.stderr
rename to crates/toml_edit/tests/fixtures/invalid/table-empty.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/table-empty.toml b/crates/toml_edit/tests/fixtures/invalid/table-empty.toml
new file mode 100644
index 00000000..fe51488c
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/table-empty.toml
@@ -0,0 +1,1 @@
+[]
diff --git a/tests/fixtures/invalid/table-nested-brackets-close.stderr b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.stderr
similarity index 100%
rename from tests/fixtures/invalid/table-nested-brackets-close.stderr
rename to crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.toml b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.toml
new file mode 100644
index 00000000..c8b5a678
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-close.toml
@@ -0,0 +1,2 @@
+[a]b]
+zyx = 42
diff --git a/tests/fixtures/invalid/table-nested-brackets-open.stderr b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.stderr
similarity index 100%
rename from tests/fixtures/invalid/table-nested-brackets-open.stderr
rename to crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.toml b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.toml
new file mode 100644
index 00000000..246d7e91
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/table-nested-brackets-open.toml
@@ -0,0 +1,2 @@
+[a[b]
+zyx = 42
diff --git a/tests/fixtures/invalid/table-whitespace.stderr b/crates/toml_edit/tests/fixtures/invalid/table-whitespace.stderr
similarity index 100%
rename from tests/fixtures/invalid/table-whitespace.stderr
rename to crates/toml_edit/tests/fixtures/invalid/table-whitespace.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/table-whitespace.toml b/crates/toml_edit/tests/fixtures/invalid/table-whitespace.toml
new file mode 100644
index 00000000..79bbcb1e
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/table-whitespace.toml
@@ -0,0 +1,1 @@
+[invalid key]
\ No newline at end of file
diff --git a/tests/fixtures/invalid/table-with-pound.stderr b/crates/toml_edit/tests/fixtures/invalid/table-with-pound.stderr
similarity index 100%
rename from tests/fixtures/invalid/table-with-pound.stderr
rename to crates/toml_edit/tests/fixtures/invalid/table-with-pound.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/table-with-pound.toml b/crates/toml_edit/tests/fixtures/invalid/table-with-pound.toml
new file mode 100644
index 00000000..0d8edb52
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/table-with-pound.toml
@@ -0,0 +1,2 @@
+[key#group]
+answer = 42
\ No newline at end of file
diff --git a/tests/fixtures/invalid/text-after-array-entries.stderr b/crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.stderr
similarity index 100%
rename from tests/fixtures/invalid/text-after-array-entries.stderr
rename to crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.toml b/crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.toml
new file mode 100644
index 00000000..1a728907
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/text-after-array-entries.toml
@@ -0,0 +1,4 @@
+array = [
+  "Is there life after an array separator?", No
+  "Entry"
+]
diff --git a/tests/fixtures/invalid/text-after-integer.stderr b/crates/toml_edit/tests/fixtures/invalid/text-after-integer.stderr
similarity index 100%
rename from tests/fixtures/invalid/text-after-integer.stderr
rename to crates/toml_edit/tests/fixtures/invalid/text-after-integer.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/text-after-integer.toml b/crates/toml_edit/tests/fixtures/invalid/text-after-integer.toml
new file mode 100644
index 00000000..42de7aff
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/text-after-integer.toml
@@ -0,0 +1,1 @@
+answer = 42 the ultimate answer?
diff --git a/tests/fixtures/invalid/text-after-string.stderr b/crates/toml_edit/tests/fixtures/invalid/text-after-string.stderr
similarity index 100%
rename from tests/fixtures/invalid/text-after-string.stderr
rename to crates/toml_edit/tests/fixtures/invalid/text-after-string.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/text-after-string.toml b/crates/toml_edit/tests/fixtures/invalid/text-after-string.toml
new file mode 100644
index 00000000..c92a6f11
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/text-after-string.toml
@@ -0,0 +1,1 @@
+string = "Is there life after strings?" No.
diff --git a/tests/fixtures/invalid/text-after-table.stderr b/crates/toml_edit/tests/fixtures/invalid/text-after-table.stderr
similarity index 100%
rename from tests/fixtures/invalid/text-after-table.stderr
rename to crates/toml_edit/tests/fixtures/invalid/text-after-table.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/text-after-table.toml b/crates/toml_edit/tests/fixtures/invalid/text-after-table.toml
new file mode 100644
index 00000000..87da9db2
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/text-after-table.toml
@@ -0,0 +1,1 @@
+[error] this shouldn't be here
diff --git a/tests/fixtures/invalid/text-before-array-separator.stderr b/crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.stderr
similarity index 100%
rename from tests/fixtures/invalid/text-before-array-separator.stderr
rename to crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.toml b/crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.toml
new file mode 100644
index 00000000..9b06a392
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/text-before-array-separator.toml
@@ -0,0 +1,4 @@
+array = [
+  "Is there life before an array separator?" No,
+  "Entry"
+]
diff --git a/tests/fixtures/invalid/text-in-array.stderr b/crates/toml_edit/tests/fixtures/invalid/text-in-array.stderr
similarity index 100%
rename from tests/fixtures/invalid/text-in-array.stderr
rename to crates/toml_edit/tests/fixtures/invalid/text-in-array.stderr
diff --git a/crates/toml_edit/tests/fixtures/invalid/text-in-array.toml b/crates/toml_edit/tests/fixtures/invalid/text-in-array.toml
new file mode 100644
index 00000000..a6a6c420
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/invalid/text-in-array.toml
@@ -0,0 +1,5 @@
+array = [
+  "Entry 1",
+  I don't belong,
+  "Entry 2",
+]
diff --git a/crates/toml_edit/tests/fixtures/valid/arrays.json b/crates/toml_edit/tests/fixtures/valid/arrays.json
new file mode 100644
index 00000000..58aedbcc
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/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/crates/toml_edit/tests/fixtures/valid/arrays.toml b/crates/toml_edit/tests/fixtures/valid/arrays.toml
new file mode 100644
index 00000000..c435f57b
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/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/tests/fixtures/valid/comments-at-eof2.json b/crates/toml_edit/tests/fixtures/valid/comments-at-eof2.json
similarity index 100%
rename from tests/fixtures/valid/comments-at-eof2.json
rename to crates/toml_edit/tests/fixtures/valid/comments-at-eof2.json
diff --git a/tests/fixtures/valid/comments-at-eof2.toml b/crates/toml_edit/tests/fixtures/valid/comments-at-eof2.toml
similarity index 100%
rename from tests/fixtures/valid/comments-at-eof2.toml
rename to crates/toml_edit/tests/fixtures/valid/comments-at-eof2.toml
diff --git a/crates/toml_edit/tests/fixtures/valid/comments-everywhere.json b/crates/toml_edit/tests/fixtures/valid/comments-everywhere.json
new file mode 100644
index 00000000..e69a2e95
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/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/tests/fixtures/valid/comments-everywhere.toml b/crates/toml_edit/tests/fixtures/valid/comments-everywhere.toml
similarity index 100%
rename from tests/fixtures/valid/comments-everywhere.toml
rename to crates/toml_edit/tests/fixtures/valid/comments-everywhere.toml
diff --git a/crates/toml_edit/tests/fixtures/valid/datetime.json b/crates/toml_edit/tests/fixtures/valid/datetime.json
new file mode 100644
index 00000000..2ca93ce9
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/valid/datetime.json
@@ -0,0 +1,3 @@
+{
+    "bestdayever": {"type": "datetime", "value": "1987-07-05T17:45:00Z"}
+}
diff --git a/crates/toml_edit/tests/fixtures/valid/datetime.toml b/crates/toml_edit/tests/fixtures/valid/datetime.toml
new file mode 100644
index 00000000..2e993407
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/valid/datetime.toml
@@ -0,0 +1,1 @@
+bestdayever = 1987-07-05T17:45:00Z
diff --git a/tests/fixtures/valid/float-exponent.json b/crates/toml_edit/tests/fixtures/valid/float-exponent.json
similarity index 100%
rename from tests/fixtures/valid/float-exponent.json
rename to crates/toml_edit/tests/fixtures/valid/float-exponent.json
diff --git a/tests/fixtures/valid/float-exponent.toml b/crates/toml_edit/tests/fixtures/valid/float-exponent.toml
similarity index 100%
rename from tests/fixtures/valid/float-exponent.toml
rename to crates/toml_edit/tests/fixtures/valid/float-exponent.toml
diff --git a/tests/fixtures/valid/inline-table.json b/crates/toml_edit/tests/fixtures/valid/inline-table.json
similarity index 100%
rename from tests/fixtures/valid/inline-table.json
rename to crates/toml_edit/tests/fixtures/valid/inline-table.json
diff --git a/tests/fixtures/valid/inline-table.toml b/crates/toml_edit/tests/fixtures/valid/inline-table.toml
similarity index 100%
rename from tests/fixtures/valid/inline-table.toml
rename to crates/toml_edit/tests/fixtures/valid/inline-table.toml
diff --git a/tests/fixtures/valid/integer-underscore.json b/crates/toml_edit/tests/fixtures/valid/integer-underscore.json
similarity index 100%
rename from tests/fixtures/valid/integer-underscore.json
rename to crates/toml_edit/tests/fixtures/valid/integer-underscore.json
diff --git a/tests/fixtures/valid/integer-underscore.toml b/crates/toml_edit/tests/fixtures/valid/integer-underscore.toml
similarity index 100%
rename from tests/fixtures/valid/integer-underscore.toml
rename to crates/toml_edit/tests/fixtures/valid/integer-underscore.toml
diff --git a/crates/toml_edit/tests/fixtures/valid/long-integer.json b/crates/toml_edit/tests/fixtures/valid/long-integer.json
new file mode 100644
index 00000000..16c331ed
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/valid/long-integer.json
@@ -0,0 +1,4 @@
+{
+    "answer": {"type": "integer", "value": "9223372036854775807"},
+    "neganswer": {"type": "integer", "value": "-9223372036854775808"}
+}
diff --git a/crates/toml_edit/tests/fixtures/valid/long-integer.toml b/crates/toml_edit/tests/fixtures/valid/long-integer.toml
new file mode 100644
index 00000000..424a13ac
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/valid/long-integer.toml
@@ -0,0 +1,2 @@
+answer = 9223372036854775807
+neganswer = -9223372036854775808
diff --git a/tests/fixtures/valid/multiline-string.json b/crates/toml_edit/tests/fixtures/valid/multiline-string.json
similarity index 100%
rename from tests/fixtures/valid/multiline-string.json
rename to crates/toml_edit/tests/fixtures/valid/multiline-string.json
diff --git a/tests/fixtures/valid/multiline-string.toml b/crates/toml_edit/tests/fixtures/valid/multiline-string.toml
similarity index 100%
rename from tests/fixtures/valid/multiline-string.toml
rename to crates/toml_edit/tests/fixtures/valid/multiline-string.toml
diff --git a/crates/toml_edit/tests/fixtures/valid/raw-multiline-string.json b/crates/toml_edit/tests/fixtures/valid/raw-multiline-string.json
new file mode 100644
index 00000000..b43cce5a
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/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/crates/toml_edit/tests/fixtures/valid/raw-multiline-string.toml b/crates/toml_edit/tests/fixtures/valid/raw-multiline-string.toml
new file mode 100644
index 00000000..8094c03e
--- /dev/null
+++ b/crates/toml_edit/tests/fixtures/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/tests/fixtures/valid/string-escapes.json b/crates/toml_edit/tests/fixtures/valid/string-escapes.json
similarity index 100%
rename from tests/fixtures/valid/string-escapes.json
rename to crates/toml_edit/tests/fixtures/valid/string-escapes.json
diff --git a/tests/fixtures/valid/string-escapes.toml b/crates/toml_edit/tests/fixtures/valid/string-escapes.toml
similarity index 100%
rename from tests/fixtures/valid/string-escapes.toml
rename to crates/toml_edit/tests/fixtures/valid/string-escapes.toml
diff --git a/tests/fixtures/valid/table-no-eol.json b/crates/toml_edit/tests/fixtures/valid/table-no-eol.json
similarity index 100%
rename from tests/fixtures/valid/table-no-eol.json
rename to crates/toml_edit/tests/fixtures/valid/table-no-eol.json
diff --git a/tests/fixtures/valid/table-no-eol.toml b/crates/toml_edit/tests/fixtures/valid/table-no-eol.toml
similarity index 100%
rename from tests/fixtures/valid/table-no-eol.toml
rename to crates/toml_edit/tests/fixtures/valid/table-no-eol.toml
diff --git a/tests/fixtures/valid/windows-path.json b/crates/toml_edit/tests/fixtures/valid/windows-path.json
similarity index 100%
rename from tests/fixtures/valid/windows-path.json
rename to crates/toml_edit/tests/fixtures/valid/windows-path.json
diff --git a/tests/fixtures/valid/windows-path.toml b/crates/toml_edit/tests/fixtures/valid/windows-path.toml
similarity index 100%
rename from tests/fixtures/valid/windows-path.toml
rename to crates/toml_edit/tests/fixtures/valid/windows-path.toml
diff --git a/tests/formatter.rs b/crates/toml_edit/tests/formatter.rs
similarity index 100%
rename from tests/formatter.rs
rename to crates/toml_edit/tests/formatter.rs
diff --git a/tests/macros.rs b/crates/toml_edit/tests/macros.rs
similarity index 100%
rename from tests/macros.rs
rename to crates/toml_edit/tests/macros.rs
diff --git a/tests/pretty.rs b/crates/toml_edit/tests/pretty.rs
similarity index 100%
rename from tests/pretty.rs
rename to crates/toml_edit/tests/pretty.rs
diff --git a/tests/serde.rs b/crates/toml_edit/tests/serde.rs
similarity index 100%
rename from tests/serde.rs
rename to crates/toml_edit/tests/serde.rs
diff --git a/tests/test_edit.rs b/crates/toml_edit/tests/test_edit.rs
similarity index 100%
rename from tests/test_edit.rs
rename to crates/toml_edit/tests/test_edit.rs
diff --git a/tests/test_invalid.rs b/crates/toml_edit/tests/test_invalid.rs
similarity index 100%
rename from tests/test_invalid.rs
rename to crates/toml_edit/tests/test_invalid.rs
diff --git a/tests/test_parse.rs b/crates/toml_edit/tests/test_parse.rs
similarity index 100%
rename from tests/test_parse.rs
rename to crates/toml_edit/tests/test_parse.rs
diff --git a/tests/test_valid.rs b/crates/toml_edit/tests/test_valid.rs
similarity index 100%
rename from tests/test_valid.rs
rename to crates/toml_edit/tests/test_valid.rs