Optimize token peek

We avoid formatting error messages in the very common case that a peek
is not matched. This is around 3x performance improvement on parsing the
rust-lang/rust repo, down from 20 seconds to 7 seconds.
diff --git a/src/token.rs b/src/token.rs
index be9b68e..685fc47 100644
--- a/src/token.rs
+++ b/src/token.rs
@@ -211,8 +211,6 @@
                 }
             }
 
-            impl_token!($name concat!("`", $token, "`"));
-
             impl std::default::Default for $name {
                 fn default() -> Self {
                     $name {
@@ -258,6 +256,20 @@
                     })
                 }
             }
+
+            #[cfg(feature = "parsing")]
+            impl Token for $name {
+                fn peek(cursor: Cursor) -> bool {
+                    parsing::peek_keyword(cursor, $token)
+                }
+
+                fn display() -> &'static str {
+                    concat!("`", $token, "`")
+                }
+            }
+
+            #[cfg(feature = "parsing")]
+            impl private::Sealed for $name {}
         )*
     };
 }
@@ -284,8 +296,6 @@
                 }
             }
 
-            impl_token!($name concat!("`", $token, "`"));
-
             impl std::default::Default for $name {
                 fn default() -> Self {
                     $name {
@@ -341,6 +351,20 @@
                     })
                 }
             }
+
+            #[cfg(feature = "parsing")]
+            impl Token for $name {
+                fn peek(cursor: Cursor) -> bool {
+                    parsing::peek_punct(cursor, $token)
+                }
+
+                fn display() -> &'static str {
+                    concat!("`", $token, "`")
+                }
+            }
+
+            #[cfg(feature = "parsing")]
+            impl private::Sealed for $name {}
         )*
     };
 }
@@ -439,6 +463,26 @@
 }
 
 #[cfg(feature = "parsing")]
+impl Token for Underscore {
+    fn peek(cursor: Cursor) -> bool {
+        if let Some((ident, _rest)) = cursor.ident() {
+            return ident == "_";
+        }
+        if let Some((punct, _rest)) = cursor.punct() {
+            return punct.as_char() == '_'
+        }
+        false
+    }
+
+    fn display() -> &'static str {
+        "`_`"
+    }
+}
+
+#[cfg(feature = "parsing")]
+impl private::Sealed for Underscore {}
+
+#[cfg(feature = "parsing")]
 impl Token for Paren {
     fn peek(cursor: Cursor) -> bool {
         lookahead::is_delimiter(cursor, Delimiter::Parenthesis)
@@ -688,6 +732,7 @@
 mod parsing {
     use proc_macro2::{Spacing, Span};
 
+    use buffer::Cursor;
     use error::{Error, Result};
     use parse::ParseStream;
     use span::FromSpans;
@@ -703,6 +748,14 @@
         })
     }
 
+    pub fn peek_keyword(cursor: Cursor, token: &str) -> bool {
+        if let Some((ident, _rest)) = cursor.ident() {
+            ident == token
+        } else {
+            false
+        }
+    }
+
     pub fn punct<S: FromSpans>(input: ParseStream, token: &str) -> Result<S> {
         let mut spans = [input.cursor().span(); 3];
         punct_helper(input, token, &mut spans)?;
@@ -734,6 +787,25 @@
             Err(Error::new(spans[0], format!("expected `{}`", token)))
         })
     }
+
+    pub fn peek_punct(mut cursor: Cursor, token: &str) -> bool {
+        for (i, ch) in token.chars().enumerate() {
+            match cursor.punct() {
+                Some((punct, rest)) => {
+                    if punct.as_char() != ch {
+                        break;
+                    } else if i == token.len() - 1 {
+                        return true;
+                    } else if punct.spacing() != Spacing::Joint {
+                        break;
+                    }
+                    cursor = rest;
+                }
+                None => break,
+            }
+        }
+        false
+    }
 }
 
 #[cfg(feature = "printing")]