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

Issue:
Migrate to combine 3.0



Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index cfca5e4f..b90bb461 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -30,8 +30,22 @@ branches:
 matrix:
   allow_failures:
     - rust: nightly
+    - rust: nightly-2017-12-08
 
   include:
+  - rust: "1.26.2"
+
+    before_script:
+    - rustup component add rustfmt-preview --toolchain 1.26.2
+
+    script:
+    - rustfmt -V
+    - cargo fmt -- --write-mode diff
+    - cargo test --all
+
+    after_success:
+    - ./.travis/coverage.sh
+
   - rust: nightly-2017-12-08
     env:
       global:
@@ -48,13 +62,10 @@ matrix:
 
     before_script:
     - bash -c '[ "$(cargo clippy --version 2>/dev/null)" == "0.0.175" ] || cargo install clippy --vers 0.0.175 --force'
-    - bash -c '[ "$(rustfmt --version 2>/dev/null)" == "0.2.17-nightly ( )" ] || cargo install rustfmt-nightly --vers 0.2.17 --force'
 
     script:
     - cargo clippy -- -D warnings -Z no-trans
-    - cargo fmt -- --write-mode=diff
     - rm -rf target/debug/{test_,toml_edit-}*
-    - cargo test
     # - RUSTFLAGS="-Z sanitizer=leak" cargo test --verbose --target x86_64-unknown-linux-gnu
 
     after_success:
diff --git a/Cargo.toml b/Cargo.toml
index 6eeec8df..1dd05ff1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ appveyor = { repository = "ordian/toml_edit" }
 [dependencies]
 chrono = "0.4"
 linked-hash-map = "0.5"
-combine = "=3.0.0-alpha.2"
+combine = "3.3.1"
 
 [dev-dependencies]
 serde_json = "1.0"
diff --git a/README.md b/README.md
index 049b1f89..279e526d 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ c = { d = "hello" }
 ## Limitations
 
 *Things it does not preserve:
-1. Different quotes and spaces around the same table key, e.g. 
+* Different quotes and spaces around the same table key, e.g.
 ```toml
 [ 'a'. b]
 [ "a"  .c]
@@ -56,8 +56,8 @@ will be represented as (spaces are removed, the first encountered quote type is
 ['a'.c]
 ['a'.d]
 ``` 
-2. Children tables before parent table (tables are reordered, see [test]).
-3. Scattered array of tables (tables are reordered, see [test]).
+* Children tables before parent table (tables are reordered, see [test]).
+* Scattered array of tables (tables are reordered, see [test]).
 
 The reason behind the first limitation is that `Table` does not store its header, 
 allowing us to safely swap two tables (we store a mapping in each table: child key -> child table).
diff --git a/src/decor.rs b/src/decor.rs
index 5d62f5ea..b37a5ffb 100644
--- a/src/decor.rs
+++ b/src/decor.rs
@@ -24,7 +24,6 @@ pub(crate) struct Decor {
 
 pub(crate) type InternalString = String;
 
-
 impl Decor {
     pub fn new<S: Into<InternalString>>(prefix: S, suffix: S) -> Self {
         Self {
diff --git a/src/display.rs b/src/display.rs
index 7f22993f..a40a5c68 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -1,18 +1,16 @@
-use std::fmt::{Display, Formatter, Result};
-use value::{Array, DateTime, InlineTable, Value};
 use decor::{Formatted, Repr};
 use document::Document;
-use table::{Item, Table};
 use std::cell::{Cell, RefCell};
+use std::fmt::{Display, Formatter, Result};
+use table::{Item, Table};
+use value::{Array, DateTime, InlineTable, Value};
 
 impl Display for Repr {
     fn fmt(&self, f: &mut Formatter) -> Result {
         write!(
             f,
             "{}{}{}",
-            self.decor.prefix,
-            self.raw_value,
-            self.decor.suffix
+            self.decor.prefix, self.raw_value, self.decor.suffix
         )
     }
 }
diff --git a/src/document.rs b/src/document.rs
index 10621470..ff390b2f 100644
--- a/src/document.rs
+++ b/src/document.rs
@@ -1,7 +1,7 @@
-use std::str::FromStr;
-use table::{Item, Iter, Table};
 use decor::InternalString;
 use parser;
+use std::str::FromStr;
+use table::{Item, Iter, Table};
 
 /// Type representing a TOML document
 #[derive(Debug, Clone)]
@@ -21,7 +21,6 @@ impl Default for Document {
     }
 }
 
-
 impl Document {
     /// Creates an empty document
     pub fn new() -> Self {
diff --git a/src/formatted.rs b/src/formatted.rs
index dba5bc7b..4194459d 100644
--- a/src/formatted.rs
+++ b/src/formatted.rs
@@ -1,12 +1,11 @@
-use value::{Array, DateTime, InlineTable, Value};
-use table::{Item, KeyValuePairs, TableKeyValue};
+use combine::stream::state::State;
 use decor::{Decor, Formatted, InternalString, Repr};
 use key::Key;
-use std::iter::FromIterator;
 use parser::strings;
 use parser::TomlError;
-use combine::{self, Parser};
-
+use std::iter::FromIterator;
+use table::{Item, KeyValuePairs, TableKeyValue};
+use value::{Array, DateTime, InlineTable, Value};
 
 pub(crate) fn decorate_array(array: &mut Array) {
     for (i, val) in array
@@ -129,18 +128,17 @@ impl From<f64> for Value {
 }
 
 macro_rules! try_parse {
-    ($s:expr, $p:expr) => (
-        {
-            let result = $p.parse(combine::State::new($s));
-            match result {
-                Ok((_, ref rest)) if !rest.input.is_empty() => {
-                    Err(TomlError::from_unparsed(rest.positioner, $s))
-                }
-                Ok((s, _)) => Ok(s),
-                Err(e) => Err(TomlError::new(e, $s)),
+    ($s:expr, $p:expr) => {{
+        use combine::Parser;
+        let result = $p.easy_parse(State::new($s));
+        match result {
+            Ok((_, ref rest)) if !rest.input.is_empty() => {
+                Err(TomlError::from_unparsed(rest.positioner, $s))
             }
+            Ok((s, _)) => Ok(s),
+            Err(e) => Err(TomlError::new(e.into(), $s)),
         }
-    );
+    }};
 }
 
 // TODO: clean this mess
diff --git a/src/index.rs b/src/index.rs
index 8266be2f..702ce3de 100644
--- a/src/index.rs
+++ b/src/index.rs
@@ -1,8 +1,8 @@
+use document::Document;
+use formatted::to_table_key_value;
 use std::ops;
 use table::{value, Item, Table};
 use value::{InlineTable, Value};
-use formatted::to_table_key_value;
-use document::Document;
 
 // copied from
 // https://github.com/serde-rs/json/blob/master/src/value/index.rs
diff --git a/src/key.rs b/src/key.rs
index 8c99501f..6dfd9e9a 100644
--- a/src/key.rs
+++ b/src/key.rs
@@ -1,9 +1,7 @@
-use std::str::FromStr;
+use combine::stream::state::State;
 use decor::InternalString;
 use parser;
-use combine;
-use combine::Parser;
-
+use std::str::FromStr;
 
 /// Key as part of a Key/Value Pair or a table header.
 ///
@@ -50,7 +48,8 @@ impl FromStr for Key {
 
 impl Key {
     fn try_parse(s: &str) -> Result<Key, parser::TomlError> {
-        let result = parser::key().parse(combine::State::new(s));
+        use combine::Parser;
+        let result = parser::key_parser().easy_parse(State::new(s));
         match result {
             Ok((_, ref rest)) if !rest.input.is_empty() => {
                 Err(parser::TomlError::from_unparsed(rest.positioner, s))
diff --git a/src/lib.rs b/src/lib.rs
index 0396009c..e654ac9e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -39,7 +39,8 @@
 //! ## Limitations
 //!
 //! *Things it does not preserve:
-//! 1. Different quotes and spaces around the same table key, e.g.
+//!
+//! * Different quotes and spaces around the same table key, e.g.
 //!
 //! ```text
 //! [ 'a'. b]
@@ -55,8 +56,8 @@
 //! ['a'.d]
 //! ```
 //!
-//! 2. Children tables before parent table (tables are reordered, see [test]).
-//! 3. Scattered array of tables (tables are reordered, see [test]).
+//! * Children tables before parent table (tables are reordered, see [test]).
+//! * Scattered array of tables (tables are reordered, see [test]).
 //!
 //! The reason behind the first limitation is that `Table` does not store its header,
 //! allowing us to safely swap two tables
@@ -65,26 +66,27 @@
 //! This last two limitations allow us to represent a toml document as a tree-like data structure,
 //! which enables easier implementation of editing operations
 //! and an easy to use and type-safe API.
+//!
+//! [test]: https://github.com/ordian/toml_edit/blob/f09bd5d075fdb7d2ef8d9bb3270a34506c276753/tests/test_valid.rs#L84
 extern crate chrono;
 #[macro_use]
 extern crate combine;
 extern crate linked_hash_map;
 
-
-pub(crate) mod formatted;
-mod value;
-mod display;
-mod parser;
-mod decor;
-mod key;
 mod array_of_tables;
-mod table;
+mod decor;
+mod display;
 mod document;
+pub(crate) mod formatted;
 mod index;
+mod key;
+mod parser;
+mod table;
+mod value;
 
-pub use value::{Array, InlineTable, Value};
+pub use array_of_tables::ArrayOfTables;
+pub use document::Document;
 pub use key::Key;
 pub use parser::TomlError;
 pub use table::{array, table, value, Item, Iter, Table, TableLike};
-pub use array_of_tables::ArrayOfTables;
-pub use document::Document;
+pub use value::{Array, InlineTable, Value};
diff --git a/src/parser/array.rs b/src/parser/array.rs
index 8adb2be3..9cc9a579 100644
--- a/src/parser/array.rs
+++ b/src/parser/array.rs
@@ -1,13 +1,13 @@
-use combine::*;
-use combine::range::recognize_with_value;
 use combine::char::char;
-use combine::primitives::RangeStream;
+use combine::range::recognize_with_value;
+use combine::stream::RangeStream;
+use combine::*;
+use decor::InternalString;
+use formatted::decorated;
 use parser::errors::CustomError;
 use parser::trivia::ws_comment_newline;
 use parser::value::value;
 use value::{Array, Value};
-use decor::InternalString;
-use formatted::decorated;
 
 // ;; Array
 
diff --git a/src/parser/datetime.rs b/src/parser/datetime.rs
index 252a3b3f..4584e33b 100644
--- a/src/parser/datetime.rs
+++ b/src/parser/datetime.rs
@@ -1,12 +1,11 @@
-use combine::*;
+use chrono;
 use combine::char::{char, digit};
 use combine::combinator::{skip_count_min_max, SkipCountMinMax};
-use chrono;
-use combine::primitives::RangeStream;
 use combine::range::{recognize, recognize_with_value};
+use combine::stream::RangeStream;
+use combine::*;
 use value;
 
-
 #[inline]
 pub fn repeat<P: Parser>(count: usize, parser: P) -> SkipCountMinMax<P> {
     skip_count_min_max(count, count, parser)
diff --git a/src/parser/document.rs b/src/parser/document.rs
index 93de9355..a1024ab6 100644
--- a/src/parser/document.rs
+++ b/src/parser/document.rs
@@ -1,89 +1,54 @@
-use combine::*;
-use combine::Parser;
 use combine::char::char;
 use combine::range::recognize;
-use combine::primitives::RangeStream;
-use parser::{TomlError, TomlParser};
-use parser::errors::CustomError;
-use parser::trivia::{comment, line_ending, line_trailing, newline, ws};
-use parser::key::key;
-use parser::value::value;
-use parser::table::table;
-use parser::inline_table::KEYVAL_SEP;
+use combine::stream::state::State;
+use combine::stream::RangeStream;
+use combine::Parser;
+use combine::*;
 use decor::{InternalString, Repr};
 use document::Document;
-use table::{Item, TableKeyValue};
 use formatted::decorated;
-use std::mem;
+use parser::errors::CustomError;
+use parser::inline_table::KEYVAL_SEP;
+use parser::key::key;
+use parser::table::table;
+use parser::trivia::{comment, line_ending, line_trailing, newline, ws};
+use parser::value::value;
+use parser::{TomlError, TomlParser};
 use std::cell::RefCell;
-// https://github.com/rust-lang/rust/issues/41358
-#[allow(unused_imports)]
+use std::mem;
 use std::ops::DerefMut;
+use table::{Item, TableKeyValue};
 
+toml_parser!(parse_comment, parser, {
+    (comment(), line_ending()).map(|(c, e)| parser.borrow_mut().deref_mut().on_comment(c, e))
+});
 
-parser!{
-    fn parse_comment['a, 'b, I](parser: &'b RefCell<TomlParser>)(I) -> ()
-        where
-        [I: RangeStream<Range = &'a str, Item = char>,]
-    {
-        (
-            comment(),
-            line_ending(),
-        ).map(|(c, e)|
-              parser
-              .borrow_mut()
-              .deref_mut()
-              .on_comment(c, e))
-    }
-}
-
-parser!{
-    fn parse_ws['a, 'b, I](parser: &'b RefCell<TomlParser>)(I) -> ()
-        where
-        [I: RangeStream<Range = &'a str, Item = char>,]
-    {
-        ws()
-            .map(|w|
-                 parser
-                 .borrow_mut()
-                 .deref_mut()
-                 .on_ws(w))
-    }
-}
+toml_parser!(parse_ws, parser, {
+    ws().map(|w| parser.borrow_mut().deref_mut().on_ws(w))
+});
 
-parser!{
-    fn parse_newline['a, 'b, I](parser: &'b RefCell<TomlParser>)(I) -> ()
-        where
-        [I: RangeStream<Range = &'a str, Item = char>,]
-    {
-        recognize(newline())
-            .map(|w|
-                 parser
-                 .borrow_mut()
-                 .deref_mut()
-                 .on_ws(w))
-    }
-}
+toml_parser!(parse_newline, parser, {
+    recognize(newline()).map(|w| parser.borrow_mut().deref_mut().on_ws(w))
+});
 
-parser!{
-    fn keyval['a, 'b, I](parser: &'b RefCell<TomlParser>)(I) -> ()
-        where
-        [I: RangeStream<Range = &'a str, Item = char>,]
-    {
-        parse_keyval().and_then(|(k, kv)|
-                                parser
-                                .borrow_mut()
-                                .deref_mut()
-                                .on_keyval(k, kv))
-    }
-}
+toml_parser!(keyval, parser, {
+    parse_keyval().and_then(|(k, kv)| parser.borrow_mut().deref_mut().on_keyval(k, kv))
+});
 
 // keyval = key keyval-sep val
 parser!{
     fn parse_keyval['a, I]()(I) -> (InternalString, TableKeyValue)
-        where
-        [I: RangeStream<Range = &'a str, Item = char>,]
-    {
+    where
+        [I: RangeStream<
+         Range = &'a str,
+         Item = char>,
+         I::Error: ParseError<char, &'a str, <I as StreamOnce>::Position>,
+         <I::Error as ParseError<char, &'a str, <I as StreamOnce>::Position>>::StreamError:
+         From<::std::num::ParseIntError> +
+         From<::std::num::ParseFloatError> +
+         From<::chrono::ParseError> +
+         From<::parser::errors::CustomError>
+    ] {
         (
             (key(), ws()),
             char(KEYVAL_SEP),
@@ -103,7 +68,6 @@ parser!{
     }
 }
 
-
 impl TomlParser {
     // ;; TOML
 
@@ -129,7 +93,7 @@ impl TomlParser {
                     )).skip(parse_ws(&parser)),
                 ),
             )))
-            .parse(input);
+            .easy_parse(input);
         match parsed {
             Ok((_, ref rest)) if !rest.input.is_empty() => {
                 Err(TomlError::from_unparsed(rest.positioner, s))
diff --git a/src/parser/errors.rs b/src/parser/errors.rs
index 46a20986..6a8e2101 100644
--- a/src/parser/errors.rs
+++ b/src/parser/errors.rs
@@ -1,9 +1,8 @@
-use std::fmt::{Display, Formatter, Result};
+use combine::easy::Errors as ParseError;
+use combine::stream::easy::Error;
+use combine::stream::state::SourcePosition;
 use std::error::Error as StdError;
-use combine::ParseError;
-use combine::state::SourcePosition;
-use combine::primitives::Error;
-
+use std::fmt::{Display, Formatter, Result};
 
 /// Type representing a TOML parse error
 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
@@ -12,7 +11,7 @@ pub struct TomlError {
 }
 
 impl TomlError {
-    pub(crate) fn new(error: ParseError<SourcePosition, char, &str>, input: &str) -> Self {
+    pub(crate) fn new(error: ParseError<char, &str, SourcePosition>, input: &str) -> Self {
         Self {
             message: format!("{}", FancyError::new(error, input)),
         }
@@ -50,15 +49,14 @@ impl StdError for TomlError {
     }
 }
 
-
 #[derive(Debug)]
 pub(crate) struct FancyError<'a> {
-    error: ParseError<SourcePosition, char, &'a str>,
+    error: ParseError<char, &'a str, SourcePosition>,
     input: &'a str,
 }
 
 impl<'a> FancyError<'a> {
-    pub(crate) fn new(error: ParseError<SourcePosition, char, &'a str>, input: &'a str) -> Self {
+    pub(crate) fn new(error: ParseError<char, &'a str, SourcePosition>, input: &'a str) -> Self {
         Self {
             error: error,
             input: input,
@@ -103,7 +101,7 @@ impl<'a> Display for FancyError<'a> {
 }
 
 #[derive(Debug, Clone)]
-pub(crate) enum CustomError {
+pub enum CustomError {
     MixedArrayType { got: String, expected: String },
     DuplicateKey { key: String, table: String },
     InvalidHexEscape(u32),
diff --git a/src/parser/inline_table.rs b/src/parser/inline_table.rs
index 07a88bd5..a17e3f6d 100644
--- a/src/parser/inline_table.rs
+++ b/src/parser/inline_table.rs
@@ -1,14 +1,14 @@
-use combine::*;
 use combine::char::char;
-use combine::primitives::RangeStream;
+use combine::stream::RangeStream;
+use combine::*;
+use decor::{InternalString, Repr};
+use formatted::decorated;
 use parser::errors::CustomError;
-use parser::trivia::ws;
 use parser::key::key;
+use parser::trivia::ws;
 use parser::value::value;
-use value::InlineTable;
-use decor::{InternalString, Repr};
 use table::{Item, TableKeyValue};
-use formatted::decorated;
+use value::InlineTable;
 
 // ;; Inline Table
 
diff --git a/src/parser/key.rs b/src/parser/key.rs
index 2fb77272..b4eca0a7 100644
--- a/src/parser/key.rs
+++ b/src/parser/key.rs
@@ -1,8 +1,8 @@
-use combine::*;
-use combine::primitives::RangeStream;
 use combine::range::{recognize_with_value, take_while1};
-use parser::strings::{basic_string, literal_string};
+use combine::stream::RangeStream;
+use combine::*;
 use decor::InternalString;
+use parser::strings::{basic_string, literal_string};
 
 #[inline]
 fn is_unquoted_char(c: char) -> bool {
diff --git a/src/parser/macros.rs b/src/parser/macros.rs
index fdedaba3..d9594624 100644
--- a/src/parser/macros.rs
+++ b/src/parser/macros.rs
@@ -5,10 +5,41 @@ macro_rules! parse (
         parser!{
             pub fn $name['a, I]($($arg : $arg_type),*)(I) -> $ret
                 where
-                [I: RangeStream<Range = &'a str, Item = char>,]
-            {
+                [I: RangeStream<
+                 Range = &'a str,
+                 Item = char>,
+                 I::Error: ParseError<char, &'a str, <I as StreamOnce>::Position>,
+                 <I::Error as ParseError<char, &'a str, <I as StreamOnce>::Position>>::StreamError:
+                 From<::std::num::ParseIntError> +
+                 From<::std::num::ParseFloatError> +
+                 From<::chrono::ParseError> +
+                 From<::parser::errors::CustomError>
+            ]            {
                 $code
             }
         }
     );
 );
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! toml_parser (
+    ($name:ident, $argh:ident, $closure:expr) => (
+        parser!{
+            fn $name['a, 'b, I]($argh: &'b RefCell<TomlParser>)(I) -> ()
+            where
+                [I: RangeStream<
+                 Range = &'a str,
+                 Item = char>,
+                 I::Error: ParseError<char, &'a str, <I as StreamOnce>::Position>,
+                 <I::Error as ParseError<char, &'a str, <I as StreamOnce>::Position>>::StreamError:
+                 From<::std::num::ParseIntError> +
+                 From<::std::num::ParseFloatError> +
+                 From<::chrono::ParseError> +
+                 From<::parser::errors::CustomError>
+            ]    {
+                $closure
+            }
+        }
+    );
+);
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index fc475d25..73fa5b7c 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -2,22 +2,21 @@
 
 #[macro_use]
 mod macros;
-mod errors;
-mod trivia;
-pub(crate) mod strings;
-mod numbers;
-mod datetime;
 mod array;
-mod inline_table;
-mod value;
-mod table;
+mod datetime;
 mod document;
+mod errors;
+mod inline_table;
 mod key;
+mod numbers;
+pub(crate) mod strings;
+mod table;
+mod trivia;
+mod value;
 
 pub use self::errors::TomlError;
-
-pub(crate) use self::key::key;
-pub(crate) use self::value::value;
+pub(crate) use self::key::key as key_parser;
+pub(crate) use self::value::value as value_parser;
 
 use document::Document;
 use table::Table;
@@ -38,59 +37,52 @@ impl Default for TomlParser {
     }
 }
 
-
 #[cfg(test)]
 mod tests {
+    use combine::stream::state::State;
+    use combine::*;
     use parser::*;
     use std;
-    use combine::*;
-
 
     macro_rules! parsed_eq {
-        ($parsed:ident, $expected:expr) => (
-            {
-                assert!($parsed.is_ok());
-                let (v, rest) = $parsed.unwrap();
-                assert_eq!(v, $expected);
-                assert!(rest.input.is_empty());
-            }
-        );
+        ($parsed:ident, $expected:expr) => {{
+            assert!($parsed.is_ok());
+            let (v, rest) = $parsed.unwrap();
+            assert_eq!(v, $expected);
+            assert!(rest.input.is_empty());
+        }};
     }
 
     macro_rules! parsed_float_eq {
-        ($input:ident, $expected:expr) => (
-            {
-                let parsed = numbers::float().parse(State::new($input));
-                assert!(parsed.is_ok());
-                let (v, rest) = parsed.unwrap();
-                assert!(($expected - v).abs() < std::f64::EPSILON);
-                assert!(rest.input.is_empty());
-            }
-        );
+        ($input:ident, $expected:expr) => {{
+            let parsed = numbers::float().easy_parse(State::new($input));
+            assert!(parsed.is_ok());
+            let (v, rest) = parsed.unwrap();
+            assert!(($expected - v).abs() < std::f64::EPSILON);
+            assert!(rest.input.is_empty());
+        }};
     }
 
     macro_rules! parsed_value_eq {
-        ($input:expr) => (
-            let parsed = value::value().parse(State::new(*$input));
+        ($input:expr) => {
+            let parsed = value::value().easy_parse(State::new(*$input));
             assert!(parsed.is_ok());
             let (v, rest) = parsed.unwrap();
             assert_eq!(v.to_string(), *$input);
             assert!(rest.input.is_empty());
-        );
+        };
     }
 
     macro_rules! parsed_date_time_eq {
-        ($input:expr, $is:ident) => (
-            {
-                let parsed = value::value().parse(State::new(*$input));
-                assert!(parsed.is_ok());
-                let (v, rest) = parsed.unwrap();
-                assert_eq!(v.to_string(), *$input);
-                assert!(rest.input.is_empty());
-                assert!(v.is_date_time());
-                assert!(v.as_date_time().unwrap().$is());
-            }
-        );
+        ($input:expr, $is:ident) => {{
+            let parsed = value::value().easy_parse(State::new(*$input));
+            assert!(parsed.is_ok());
+            let (v, rest) = parsed.unwrap();
+            assert_eq!(v.to_string(), *$input);
+            assert!(rest.input.is_empty());
+            assert!(v.is_date_time());
+            assert!(v.as_date_time().unwrap().$is());
+        }};
     }
 
     #[test]
@@ -107,12 +99,12 @@ mod tests {
             (&std::i64::MAX.to_string()[..], std::i64::MAX),
         ];
         for &(input, expected) in &cases {
-            let parsed = numbers::integer().parse(State::new(input));
+            let parsed = numbers::integer().easy_parse(State::new(input));
             parsed_eq!(parsed, expected);
         }
 
         let overflow = "1000000000000000000000000000000000";
-        let parsed = numbers::integer().parse(State::new(overflow));
+        let parsed = numbers::integer().easy_parse(State::new(overflow));
         assert!(parsed.is_err());
     }
 
@@ -140,7 +132,7 @@ mod tests {
     fn basic_string() {
         let input =
             r#""I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF. \U0002070E""#;
-        let parsed = strings::string().parse(State::new(input));
+        let parsed = strings::string().easy_parse(State::new(input));
         parsed_eq!(
             parsed,
             "I\'m a string. \"You can quote me\". Name\tJosé\nLocation\tSF. \u{2070E}"
@@ -162,14 +154,14 @@ Violets are blue"#,
         ];
 
         for &(input, expected) in &cases {
-            let parsed = strings::string().parse(State::new(input));
+            let parsed = strings::string().easy_parse(State::new(input));
             parsed_eq!(parsed, expected);
         }
 
         let invalid_cases = [r#""""  """#, r#""""  \""""#];
 
         for input in &invalid_cases {
-            let parsed = strings::ml_basic_string().parse(State::new(*input));
+            let parsed = strings::ml_basic_string().easy_parse(State::new(*input));
             assert!(parsed.is_err());
         }
     }
@@ -190,7 +182,7 @@ The quick brown \
        """"#,
         ];
         for input in &inputs {
-            let parsed = strings::string().parse(State::new(*input));
+            let parsed = strings::string().easy_parse(State::new(*input));
             parsed_eq!(parsed, "The quick brown fox jumps over the lazy dog.");
         }
         let empties = [
@@ -202,7 +194,7 @@ The quick brown \
 """"#,
         ];
         for empty in &empties {
-            let parsed = strings::string().parse(State::new(*empty));
+            let parsed = strings::string().easy_parse(State::new(*empty));
             parsed_eq!(parsed, "");
         }
     }
@@ -217,7 +209,7 @@ The quick brown \
         ];
 
         for input in &inputs {
-            let parsed = strings::string().parse(State::new(*input));
+            let parsed = strings::string().easy_parse(State::new(*input));
             parsed_eq!(parsed, &input[1..input.len() - 1]);
         }
     }
@@ -225,7 +217,7 @@ The quick brown \
     #[test]
     fn ml_literal_string() {
         let input = r#"'''I [dw]on't need \d{2} apples'''"#;
-        let parsed = strings::string().parse(State::new(input));
+        let parsed = strings::string().easy_parse(State::new(input));
         parsed_eq!(parsed, &input[3..input.len() - 3]);
         let input = r#"'''
 The first newline is
@@ -233,7 +225,7 @@ trimmed in raw strings.
    All other whitespace
    is preserved.
 '''"#;
-        let parsed = strings::string().parse(State::new(input));
+        let parsed = strings::string().easy_parse(State::new(input));
         parsed_eq!(parsed, &input[4..input.len() - 3]);
     }
 
@@ -296,7 +288,7 @@ trimmed in raw strings.
    "#,
         ];
         for input in &inputs {
-            let parsed = trivia::ws_comment_newline().parse(State::new(*input));
+            let parsed = trivia::ws_comment_newline().easy_parse(State::new(*input));
             assert!(parsed.is_ok());
             let (t, rest) = parsed.unwrap();
             assert!(rest.input.is_empty());
@@ -347,7 +339,7 @@ trimmed in raw strings.
 
         let invalid_inputs = [r#"["#, r#"[,]"#, r#"[,2]"#, r#"[1e165,,]"#, r#"[ 1, 2.0 ]"#];
         for input in &invalid_inputs {
-            let parsed = array::array().parse(State::new(*input));
+            let parsed = array::array().easy_parse(State::new(*input));
             assert!(parsed.is_err());
         }
     }
@@ -365,7 +357,7 @@ trimmed in raw strings.
         }
         let invalid_inputs = [r#"{a = 1e165"#, r#"{ hello = "world", a = 2, hello = 1}"#];
         for input in &invalid_inputs {
-            let parsed = inline_table::inline_table().parse(State::new(*input));
+            let parsed = inline_table::inline_table().easy_parse(State::new(*input));
             assert!(parsed.is_err());
         }
     }
@@ -379,7 +371,7 @@ trimmed in raw strings.
         ];
 
         for &(input, expected) in &cases {
-            let parsed = key::key().parse(State::new(input));
+            let parsed = key::key().easy_parse(State::new(input));
             assert!(parsed.is_ok());
             let ((.., k), rest) = parsed.unwrap();
             assert_eq!(k, expected);
@@ -472,10 +464,8 @@ that
             assert_eq!(&doc.to_string(), document);
         }
 
-        let invalid_inputs = [
-            r#" hello = 'darkness' # my old friend
-$"#,
-        ];
+        let invalid_inputs = [r#" hello = 'darkness' # my old friend
+$"#];
         for document in &invalid_inputs {
             let doc = TomlParser::parse(document);
 
diff --git a/src/parser/numbers.rs b/src/parser/numbers.rs
index 9a3d96ec..f59f613f 100644
--- a/src/parser/numbers.rs
+++ b/src/parser/numbers.rs
@@ -1,7 +1,7 @@
-use combine::*;
 use combine::char::{char, digit};
 use combine::range::{range, recognize};
-use combine::primitives::RangeStream;
+use combine::stream::RangeStream;
+use combine::*;
 
 // ;; Boolean
 
@@ -40,7 +40,6 @@ parse!(integer() -> i64, {
         .message("While parsing an Integer")
 });
 
-
 // ;; Float
 
 // frac = decimal-point zero-prefixable-int
diff --git a/src/parser/strings.rs b/src/parser/strings.rs
index ec85aca5..7c453824 100644
--- a/src/parser/strings.rs
+++ b/src/parser/strings.rs
@@ -1,13 +1,13 @@
-use combine::*;
 use combine::char::char;
+use combine::error::{Consumed, Info};
 use combine::range::{range, take, take_while};
-use combine::primitives::{Consumed, RangeStream};
+use combine::stream::RangeStream;
+use combine::*;
 use decor::InternalString;
-use parser::trivia::{newline, ws, ws_newlines};
 use parser::errors::CustomError;
+use parser::trivia::{newline, ws, ws_newlines};
 use std::char;
 
-
 // ;; String
 
 // string = ml-basic-string / basic-string / ml-literal-string / literal-string
@@ -53,15 +53,15 @@ parse!(escape() -> char, {
         .then(|c| {
             parser(move |input| {
                 match c {
-                    'b'  => Ok(('\u{8}', Consumed::Empty(input))),
-                    'f'  => Ok(('\u{c}', Consumed::Empty(input))),
-                    'n'  => Ok(('\n',    Consumed::Empty(input))),
-                    'r'  => Ok(('\r',    Consumed::Empty(input))),
-                    't'  => Ok(('\t',    Consumed::Empty(input))),
+                    'b'  => Ok(('\u{8}', Consumed::Empty(()))),
+                    'f'  => Ok(('\u{c}', Consumed::Empty(()))),
+                    'n'  => Ok(('\n',    Consumed::Empty(()))),
+                    'r'  => Ok(('\r',    Consumed::Empty(()))),
+                    't'  => Ok(('\t',    Consumed::Empty(()))),
                     'u'  => hexescape(4).parse_stream(input),
                     'U'  => hexescape(8).parse_stream(input),
                     // ['\\', '"', '/']
-                    _    => Ok((c,       Consumed::Empty(input))),
+                    _    => Ok((c,       Consumed::Empty(()))),
                 }
             })
         })
@@ -73,7 +73,6 @@ parse!(hexescape(n: usize) -> char, {
         .and_then(|h| char::from_u32(h).ok_or_else(|| CustomError::InvalidHexEscape(h)))
 });
 
-
 // escape = %x5C                    ; \
 const ESCAPE: char = '\\';
 
@@ -83,7 +82,7 @@ parse!(basic_char() -> char, {
         .then(|c| parser(move |input| {
             match c {
                 ESCAPE => escape().parse_stream(input),
-                _      => Ok((c, Consumed::Empty(input))),
+                _      => Ok((c, Consumed::Empty(()))),
             }
         }))
 });
@@ -118,7 +117,7 @@ parse!(ml_basic_char() -> char, {
         .then(|c| parser(move |input| {
             match c {
                 ESCAPE => escape().parse_stream(input),
-                _      => Ok((c, Consumed::Empty(input))),
+                _      => Ok((c, Consumed::Empty(()))),
             }
         }))
 });
@@ -142,7 +141,7 @@ parse!(ml_basic_body() -> InternalString, {
         .skip(try_eat_escaped_newline())
         .with(
             many(
-                not_followed_by(range(ML_BASIC_STRING_DELIM))
+                not_followed_by(range(ML_BASIC_STRING_DELIM).map(Info::Range))
                     .with(
                         choice((
                             // `TOML parsers should feel free to normalize newline
@@ -158,7 +157,8 @@ parse!(ml_basic_body() -> InternalString, {
 
 // ml-basic-string = ml-basic-string-delim ml-basic-body ml-basic-string-delim
 parse!(ml_basic_string() -> InternalString, {
-    between(range(ML_BASIC_STRING_DELIM), range(ML_BASIC_STRING_DELIM),
+    between(range(ML_BASIC_STRING_DELIM),
+            range(ML_BASIC_STRING_DELIM),
             ml_basic_body())
         .message("While parsing a Multiline Basic String")
 });
@@ -204,7 +204,7 @@ parse!(ml_literal_body() -> InternalString, {
     optional(newline())
         .with(
             many(
-                not_followed_by(range(ML_LITERAL_STRING_DELIM))
+                not_followed_by(range(ML_LITERAL_STRING_DELIM).map(Info::Range))
                     .with(
                         choice((
                             // `TOML parsers should feel free to normalize newline
@@ -217,10 +217,10 @@ parse!(ml_literal_body() -> InternalString, {
         )
 });
 
-
 // ml-literal-string = ml-literal-string-delim ml-literal-body ml-literal-string-delim
 parse!(ml_literal_string() -> InternalString, {
-    between(range(ML_LITERAL_STRING_DELIM), range(ML_LITERAL_STRING_DELIM),
+    between(range(ML_LITERAL_STRING_DELIM),
+            range(ML_LITERAL_STRING_DELIM),
             ml_literal_body())
         .message("While parsing a Multiline Literal String")
 });
diff --git a/src/parser/table.rs b/src/parser/table.rs
index a445e687..419a24e8 100644
--- a/src/parser/table.rs
+++ b/src/parser/table.rs
@@ -1,22 +1,21 @@
-use combine::*;
+use array_of_tables::ArrayOfTables;
 use combine::char::char;
 use combine::range::range;
-use combine::primitives::RangeStream;
-use parser::TomlParser;
+use combine::stream::RangeStream;
+use combine::*;
+use decor::{Decor, InternalString};
+use key::Key;
 use parser::errors::CustomError;
-use parser::trivia::{line_trailing, ws};
 use parser::key::key;
-use key::Key;
-use array_of_tables::ArrayOfTables;
-use decor::{Decor, InternalString};
-use table::{Item, Table};
-use std::mem;
+use parser::trivia::{line_trailing, ws};
+use parser::TomlParser;
 use std::cell::RefCell;
+use std::mem;
+use table::{Item, Table};
 // https://github.com/rust-lang/rust/issues/41358
 #[allow(unused_imports)]
 use std::ops::DerefMut;
 
-
 // table-key-sep   = ws %x2E ws  ; . Period
 const TABLE_KEY_SEP: char = '.';
 // std-table-open  = %x5B ws     ; [ Left square bracket
@@ -28,7 +27,6 @@ const ARRAY_TABLE_OPEN: &str = "[[";
 // array-table-close = ws %x5D.5D  ; ]] Double right quare bracket
 const ARRAY_TABLE_CLOSE: &str = "]]";
 
-
 // note: this rule is not present in the original grammar
 // key-path = key *( table-key-sep key)
 parse!(key_path() -> Vec<Key>, {
@@ -36,58 +34,46 @@ parse!(key_path() -> Vec<Key>, {
             char(TABLE_KEY_SEP))
 });
 
-
 // ;; Standard Table
 
 // std-table = std-table-open key *( table-key-sep key) std-table-close
-parser!{
-    fn std_table['a, 'b, I](parser: &'b RefCell<TomlParser>)(I) -> ()
-        where
-        [I: RangeStream<Range = &'a str, Item = char>,]
-    {
-        (
-            between(char(STD_TABLE_OPEN), char(STD_TABLE_CLOSE),
-                    key_path()),
-            line_trailing(),
-        )
-            .and_then(|(h, t)|
-                      parser
-                      .borrow_mut()
-                      .deref_mut()
-                      .on_std_header(&h, t))
-    }
-}
+toml_parser!(std_table, parser, {
+    (
+        between(char(STD_TABLE_OPEN), char(STD_TABLE_CLOSE), key_path()),
+        line_trailing(),
+    ).and_then(|(h, t)| parser.borrow_mut().deref_mut().on_std_header(&h, t))
+});
 
 // ;; Array Table
 
 // array-table = array-table-open key *( table-key-sep key) array-table-close
-parser!{
-    fn array_table['a, 'b, I](parser: &'b RefCell<TomlParser>)(I) -> ()
-        where
-        [I: RangeStream<Range = &'a str, Item = char>,]
-    {
-        (
-            between(range(ARRAY_TABLE_OPEN), range(ARRAY_TABLE_CLOSE),
-                    key_path()),
-            line_trailing(),
-        )
-            .and_then(|(h, t)|
-                      parser
-                      .borrow_mut()
-                      .deref_mut()
-                      .on_array_header(&h, t))
-    }
-}
-
+toml_parser!(array_table, parser, {
+    (
+        between(
+            range(ARRAY_TABLE_OPEN),
+            range(ARRAY_TABLE_CLOSE),
+            key_path(),
+        ),
+        line_trailing(),
+    ).and_then(|(h, t)| parser.borrow_mut().deref_mut().on_array_header(&h, t))
+});
 
 // ;; Table
 
 // table = std-table / array-table
 parser!{
     pub fn table['a, 'b, I](parser: &'b RefCell<TomlParser>)(I) -> ()
-        where
-        [I: RangeStream<Range = &'a str, Item = char>,]
-    {
+    where
+        [I: RangeStream<
+         Range = &'a str,
+         Item = char>,
+         I::Error: ParseError<char, &'a str, <I as StreamOnce>::Position>,
+         <I::Error as ParseError<char, &'a str, <I as StreamOnce>::Position>>::StreamError:
+         From<::std::num::ParseIntError> +
+         From<::std::num::ParseFloatError> +
+         From<::chrono::ParseError> +
+         From<::parser::errors::CustomError>
+    ]    {
         array_table(parser)
             .or(std_table(parser))
             .message("While parsing a Table Header")
diff --git a/src/parser/trivia.rs b/src/parser/trivia.rs
index 47528b38..2b5d0424 100644
--- a/src/parser/trivia.rs
+++ b/src/parser/trivia.rs
@@ -1,7 +1,7 @@
-use combine::*;
 use combine::char::{char, crlf, newline as lf};
 use combine::range::{recognize, take_while, take_while1};
-use combine::primitives::RangeStream;
+use combine::stream::RangeStream;
+use combine::*;
 
 // wschar = ( %x20 /              ; Space
 //            %x09 )              ; Horizontal tab
@@ -30,7 +30,6 @@ fn is_non_eol(c: char) -> bool {
 // comment-start-symbol = %x23 ; #
 const COMMENT_START_SYMBOL: char = '#';
 
-
 // comment = comment-start-symbol *non-eol
 parse!(comment() -> &'a str, {
     recognize((
@@ -57,7 +56,6 @@ parse!(ws_newline() -> &'a str, {
     )
 });
 
-
 // ws-newlines      = newline *( wschar / newline )
 parse!(ws_newlines() -> &'a str, {
     recognize((
@@ -66,7 +64,6 @@ parse!(ws_newlines() -> &'a str, {
     ))
 });
 
-
 // note: this rule is not present in the original grammar
 // ws-comment-newline = *( ws-newline-nonempty / comment )
 parse!(ws_comment_newline() -> &'a str, {
diff --git a/src/parser/value.rs b/src/parser/value.rs
index 3efdc776..743c21c6 100644
--- a/src/parser/value.rs
+++ b/src/parser/value.rs
@@ -1,15 +1,14 @@
-use combine::*;
-use combine::primitives::RangeStream;
 use combine::range::recognize_with_value;
-use parser::strings::string;
+use combine::stream::RangeStream;
+use combine::*;
+use decor::{Formatted, Repr};
+use formatted;
+use parser::array::array;
 use parser::datetime::date_time;
-use parser::numbers::{boolean, float, integer};
 use parser::inline_table::inline_table;
-use parser::array::array;
+use parser::numbers::{boolean, float, integer};
+use parser::strings::string;
 use value as v;
-use decor::{Formatted, Repr};
-use formatted;
-
 
 // val = string / boolean / array / inline-table / date-time / float / integer
 parse!(value() -> v::Value, {
diff --git a/src/table.rs b/src/table.rs
index 3470f37b..3bd5dbf1 100644
--- a/src/table.rs
+++ b/src/table.rs
@@ -1,9 +1,9 @@
-use linked_hash_map::LinkedHashMap;
-use value::{sort_key_value_pairs, Array, DateTime, InlineTable, Value};
-use decor::{Decor, InternalString, Repr};
-use key::Key;
 use array_of_tables::ArrayOfTables;
+use decor::{Decor, InternalString, Repr};
 use formatted::{decorated, key_repr};
+use key::Key;
+use linked_hash_map::LinkedHashMap;
+use value::{sort_key_value_pairs, Array, DateTime, InlineTable, Value};
 
 // TODO: add method to convert a table into inline table
 
diff --git a/src/value.rs b/src/value.rs
index 506ed56f..f6b2feb1 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -1,15 +1,13 @@
-use std::str::FromStr;
-use std::mem;
 use chrono::{self, FixedOffset};
-use formatted;
-use linked_hash_map::LinkedHashMap;
+use combine::stream::state::State;
 use decor::{Decor, Formatted, InternalString};
+use formatted;
 use key::Key;
-use table::{Item, Iter, KeyValuePairs, TableKeyValue, TableLike};
+use linked_hash_map::LinkedHashMap;
 use parser;
-use combine;
-use combine::Parser;
-
+use std::mem;
+use std::str::FromStr;
+use table::{Item, Iter, KeyValuePairs, TableKeyValue, TableLike};
 
 /// Representation of a TOML Value (as part of a Key/Value Pair).
 #[derive(Debug, Clone)]
@@ -432,7 +430,8 @@ impl FromStr for Value {
 
     /// Parses a value from a &str
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let parsed = parser::value().parse(combine::State::new(s));
+        use combine::Parser;
+        let parsed = parser::value_parser().easy_parse(State::new(s));
         match parsed {
             Ok((_, ref rest)) if !rest.input.is_empty() => {
                 Err(Self::Err::from_unparsed(rest.positioner, s))


Test patch:
diff --git a/tests/test_edit.rs b/tests/test_edit.rs
index 1cf78af7..f96e34da 100644
--- a/tests/test_edit.rs
+++ b/tests/test_edit.rs
@@ -1,22 +1,18 @@
 extern crate toml_edit;
 
 macro_rules! parse_key {
-    ($s:expr) => (
-        {
-            let key = $s.parse::<Key>();
-            assert!(key.is_ok());
-            &key.unwrap()
-        }
-    );
+    ($s:expr) => {{
+        let key = $s.parse::<Key>();
+        assert!(key.is_ok());
+        &key.unwrap()
+    }};
 }
 
 macro_rules! as_table {
-    ($e:ident) => (
-        {
-            assert!($e.is_table());
-            $e.as_table_mut().unwrap()
-        }
-    );
+    ($e:ident) => {{
+        assert!($e.is_table());
+        $e.as_table_mut().unwrap()
+    }};
 }
 
 // rusfmt, U Can't Touch This
diff --git a/tests/test_parse.rs b/tests/test_parse.rs
index 5decb561..8b335401 100644
--- a/tests/test_parse.rs
+++ b/tests/test_parse.rs
@@ -3,37 +3,33 @@ extern crate toml_edit;
 use toml_edit::{Key, Value};
 
 macro_rules! parse {
-    ($s:expr, $ty:ty) => (
-        {
-            let v = $s.parse::<$ty>();
-            assert!(v.is_ok());
-            v.unwrap()
-        }
-    );
+    ($s:expr, $ty:ty) => {{
+        let v = $s.parse::<$ty>();
+        assert!(v.is_ok());
+        v.unwrap()
+    }};
 }
 
 macro_rules! parse_value {
-    ($s:expr) => (parse!($s, Value));
+    ($s:expr) => {
+        parse!($s, Value)
+    };
 }
 
 macro_rules! test_key {
-    ($s:expr, $expected:expr) => (
-        {
-            let key = parse!($s, Key);
-            assert_eq!(key.get(), $expected);
-        }
-    );
+    ($s:expr, $expected:expr) => {{
+        let key = parse!($s, Key);
+        assert_eq!(key.get(), $expected);
+    }};
 }
 
 macro_rules! parse_error {
-    ($input:expr, $ty:ty, $err_msg:expr) => (
-        {
-            let res = $input.parse::<$ty>();
-            assert!(res.is_err());
-            let err = res.unwrap_err();
-            assert!(err.to_string().find($err_msg).is_some());
-        }
-    );
+    ($input:expr, $ty:ty, $err_msg:expr) => {{
+        let res = $input.parse::<$ty>();
+        assert!(res.is_err());
+        let err = res.unwrap_err();
+        assert!(err.to_string().find($err_msg).is_some());
+    }};
 }
 
 #[test]
diff --git a/tests/test_valid.rs b/tests/test_valid.rs
index 86294e22..4515fd30 100644
--- a/tests/test_valid.rs
+++ b/tests/test_valid.rs
@@ -1,10 +1,10 @@
 extern crate serde_json;
 extern crate toml_edit;
 
-use serde_json::Value as Json;
 use serde_json::Map as JsonMap;
+use serde_json::Value as Json;
 
-use toml_edit::{Document, Item, Value, Iter};
+use toml_edit::{Document, Item, Iter, Value};
 
 fn pair_to_json((key, value): (&str, Item)) -> (String, Json) {
     fn typed_json(s: &str, json: Json) -> Json {
@@ -31,9 +31,11 @@ fn pair_to_json((key, value): (&str, Item)) -> (String, Json) {
     }
     let json = match value {
         Item::Value(ref v) => value_to_json(v),
-        Item::ArrayOfTables(ref arr) => {
-            Json::Array(arr.iter().map(|t| to_json(iter_to_owned(t.iter()))).collect::<Vec<_>>())
-        }
+        Item::ArrayOfTables(ref arr) => Json::Array(
+            arr.iter()
+                .map(|t| to_json(iter_to_owned(t.iter())))
+                .collect::<Vec<_>>(),
+        ),
         Item::Table(ref table) => to_json(iter_to_owned(table.iter())),
         Item::None => Json::Null,
     };
@@ -41,9 +43,7 @@ fn pair_to_json((key, value): (&str, Item)) -> (String, Json) {
 }
 
 fn iter_to_owned(iter: Iter) -> OwnedIter {
-    Box::new(
-        iter.map(|(k, v)| (k, v.clone()))
-    )
+    Box::new(iter.map(|(k, v)| (k, v.clone())))
 }
 
 type OwnedIter<'s> = Box<Iterator<Item = (&'s str, Item)> + 's>;