about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJeffrey Seyfried <jeffrey.seyfried@gmail.com>2017-03-14 22:04:46 +0000
committerJeffrey Seyfried <jeffrey.seyfried@gmail.com>2017-03-15 23:05:02 +0000
commitce616a7d6ad838aacd080b47566c15e82ad8dd6d (patch)
tree9f91891e12094eb0d0e96e564711c8905859f3d4
parentd9cf601ae822d07412a79aeedeabb802aa94cb34 (diff)
downloadrust-ce616a7d6ad838aacd080b47566c15e82ad8dd6d.tar.gz
rust-ce616a7d6ad838aacd080b47566c15e82ad8dd6d.zip
Improve the `TokenStream` quoter.
-rw-r--r--src/libproc_macro_plugin/lib.rs78
-rw-r--r--src/libproc_macro_plugin/quote.rs (renamed from src/libproc_macro_plugin/qquote.rs)26
-rw-r--r--src/libsyntax/tokenstream.rs6
-rw-r--r--src/test/run-pass-fulldeps/auxiliary/cond_plugin.rs5
-rw-r--r--src/test/run-pass-fulldeps/auxiliary/hello_macro.rs7
-rw-r--r--src/test/run-pass-fulldeps/auxiliary/proc_macro_def.rs10
-rw-r--r--src/test/run-pass-fulldeps/macro-quote-1.rs4
-rw-r--r--src/test/run-pass-fulldeps/macro-quote-test.rs (renamed from src/test/run-pass-fulldeps/macro-quote-empty-delims.rs)0
8 files changed, 77 insertions, 59 deletions
diff --git a/src/libproc_macro_plugin/lib.rs b/src/libproc_macro_plugin/lib.rs
index e9042909576..a6dad641253 100644
--- a/src/libproc_macro_plugin/lib.rs
+++ b/src/libproc_macro_plugin/lib.rs
@@ -13,62 +13,64 @@
 //! A library for procedural macro writers.
 //!
 //! ## Usage
-//! This crate provides the `qquote!` macro for syntax creation.
+//! This crate provides the `quote!` macro for syntax creation.
 //!
-//! The `qquote!` macro uses the crate `syntax`, so users must declare `extern crate syntax;`
+//! The `quote!` macro uses the crate `syntax`, so users must declare `extern crate syntax;`
 //! at the crate root. This is a temporary solution until we have better hygiene.
 //!
 //! ## Quasiquotation
 //!
 //! The quasiquoter creates output that, when run, constructs the tokenstream specified as
-//! input. For example, `qquote!(5 + 5)` will produce a program, that, when run, will
+//! input. For example, `quote!(5 + 5)` will produce a program, that, when run, will
 //! construct the TokenStream `5 | + | 5`.
 //!
 //! ### Unquoting
 //!
-//! Unquoting is currently done as `unquote`, and works by taking the single next
-//! TokenTree in the TokenStream as the unquoted term. Ergonomically, `unquote(foo)` works
-//! fine, but `unquote foo` is also supported.
+//! Unquoting is done with `$`, and works by taking the single next ident as the unquoted term.
+//! To quote `$` itself, use `$$`.
 //!
-//! A simple example might be:
+//! A simple example is:
 //!
 //!```
 //!fn double(tmp: TokenStream) -> TokenStream {
-//!    qquote!(unquote(tmp) * 2)
+//!    quote!($tmp * 2)
 //!}
 //!```
 //!
-//! ### Large Example: Implementing Scheme's `cond`
+//! ### Large example: Scheme's `cond`
 //!
-//! Below is the full implementation of Scheme's `cond` operator.
+//! Below is an example implementation of Scheme's `cond`.
 //!
 //! ```
-//! fn cond_rec(input: TokenStream) -> TokenStream {
-//!   if input.is_empty() { return quote!(); }
-//!
-//!   let next = input.slice(0..1);
-//!   let rest = input.slice_from(1..);
-//!
-//!   let clause : TokenStream = match next.maybe_delimited() {
-//!     Some(ts) => ts,
-//!     _ => panic!("Invalid input"),
-//!   };
-//!
-//!   // clause is ([test]) [rhs]
-//!   if clause.len() < 2 { panic!("Invalid macro usage in cond: {:?}", clause) }
-//!
-//!   let test: TokenStream = clause.slice(0..1);
-//!   let rhs: TokenStream = clause.slice_from(1..);
-//!
-//!   if ident_eq(&test[0], str_to_ident("else")) || rest.is_empty() {
-//!     quote!({unquote(rhs)})
-//!   } else {
-//!     quote!({if unquote(test) { unquote(rhs) } else { cond!(unquote(rest)) } })
-//!   }
+//! fn cond(input: TokenStream) -> TokenStream {
+//!     let mut conds = Vec::new();
+//!     let mut input = input.trees().peekable();
+//!     while let Some(tree) = input.next() {
+//!         let mut cond = match tree {
+//!             TokenTree::Delimited(_, ref delimited) => delimited.stream(),
+//!             _ => panic!("Invalid input"),
+//!         };
+//!         let mut trees = cond.trees();
+//!         let test = trees.next();
+//!         let rhs = trees.collect::<TokenStream>();
+//!         if rhs.is_empty() {
+//!             panic!("Invalid macro usage in cond: {}", cond);
+//!         }
+//!         let is_else = match test {
+//!             Some(TokenTree::Token(_, Token::Ident(ident))) if ident.name == "else" => true,
+//!             _ => false,
+//!         };
+//!         conds.push(if is_else || input.peek().is_none() {
+//!             quote!({ $rhs })
+//!         } else {
+//!             let test = test.unwrap();
+//!             quote!(if $test { $rhs } else)
+//!         });
+//!     }
+//!
+//!     conds.into_iter().collect()
 //! }
 //! ```
-//!
-
 #![crate_name = "proc_macro_plugin"]
 #![unstable(feature = "rustc_private", issue = "27812")]
 #![feature(plugin_registrar)]
@@ -87,8 +89,8 @@ extern crate rustc_plugin;
 extern crate syntax;
 extern crate syntax_pos;
 
-mod qquote;
-use qquote::qquote;
+mod quote;
+use quote::quote;
 
 use rustc_plugin::Registry;
 use syntax::ext::base::SyntaxExtension;
@@ -99,6 +101,6 @@ use syntax::symbol::Symbol;
 
 #[plugin_registrar]
 pub fn plugin_registrar(reg: &mut Registry) {
-    reg.register_syntax_extension(Symbol::intern("qquote"),
-                                  SyntaxExtension::ProcMacro(Box::new(qquote)));
+    reg.register_syntax_extension(Symbol::intern("quote"),
+                                  SyntaxExtension::ProcMacro(Box::new(quote)));
 }
diff --git a/src/libproc_macro_plugin/qquote.rs b/src/libproc_macro_plugin/quote.rs
index 0276587ed52..ad71584b61a 100644
--- a/src/libproc_macro_plugin/qquote.rs
+++ b/src/libproc_macro_plugin/quote.rs
@@ -19,7 +19,7 @@ use syntax_pos::DUMMY_SP;
 
 use std::iter;
 
-pub fn qquote<'cx>(stream: TokenStream) -> TokenStream {
+pub fn quote<'cx>(stream: TokenStream) -> TokenStream {
     stream.quote()
 }
 
@@ -72,28 +72,32 @@ impl Quote for TokenStream {
             return quote!(::syntax::tokenstream::TokenStream::empty());
         }
 
-        struct Quote(iter::Peekable<tokenstream::Cursor>);
+        struct Quoter(iter::Peekable<tokenstream::Cursor>);
 
-        impl Iterator for Quote {
+        impl Iterator for Quoter {
             type Item = TokenStream;
 
             fn next(&mut self) -> Option<TokenStream> {
-                let is_unquote = match self.0.peek() {
-                    Some(&TokenTree::Token(_, Token::Ident(ident))) if ident.name == "unquote" => {
-                        self.0.next();
-                        true
+                let quoted_tree = if let Some(&TokenTree::Token(_, Token::Dollar)) = self.0.peek() {
+                    self.0.next();
+                    match self.0.next() {
+                        Some(tree @ TokenTree::Token(_, Token::Ident(..))) => Some(tree.into()),
+                        Some(tree @ TokenTree::Token(_, Token::Dollar)) => Some(tree.quote()),
+                        // FIXME(jseyfried): improve these diagnostics
+                        Some(..) => panic!("`$` must be followed by an ident or `$` in `quote!`"),
+                        None => panic!("unexpected trailing `$` in `quote!`"),
                     }
-                    _ => false,
+                } else {
+                    self.0.next().as_ref().map(Quote::quote)
                 };
 
-                self.0.next().map(|tree| {
-                    let quoted_tree = if is_unquote { tree.into() } else { tree.quote() };
+                quoted_tree.map(|quoted_tree| {
                     quote!(::syntax::tokenstream::TokenStream::from((unquote quoted_tree)),)
                 })
             }
         }
 
-        let quoted = Quote(self.trees().peekable()).collect::<TokenStream>();
+        let quoted = Quoter(self.trees().peekable()).collect::<TokenStream>();
         quote!([(unquote quoted)].iter().cloned().collect::<::syntax::tokenstream::TokenStream>())
     }
 }
diff --git a/src/libsyntax/tokenstream.rs b/src/libsyntax/tokenstream.rs
index 2da442a1a53..8ce45f3fd08 100644
--- a/src/libsyntax/tokenstream.rs
+++ b/src/libsyntax/tokenstream.rs
@@ -162,6 +162,12 @@ impl From<TokenTree> for TokenStream {
     }
 }
 
+impl From<Token> for TokenStream {
+    fn from(token: Token) -> TokenStream {
+        TokenTree::Token(DUMMY_SP, token).into()
+    }
+}
+
 impl<T: Into<TokenStream>> iter::FromIterator<T> for TokenStream {
     fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
         TokenStream::concat(iter.into_iter().map(Into::into).collect::<Vec<_>>())
diff --git a/src/test/run-pass-fulldeps/auxiliary/cond_plugin.rs b/src/test/run-pass-fulldeps/auxiliary/cond_plugin.rs
index 2f94a440e72..0433b95865e 100644
--- a/src/test/run-pass-fulldeps/auxiliary/cond_plugin.rs
+++ b/src/test/run-pass-fulldeps/auxiliary/cond_plugin.rs
@@ -49,9 +49,10 @@ fn cond(input: TokenStream) -> TokenStream {
             _ => false,
         };
         conds.push(if is_else || input.peek().is_none() {
-            qquote!({ unquote rhs })
+            quote!({ $rhs })
         } else {
-            qquote!(if unquote(test.unwrap()) { unquote rhs } else)
+            let test = test.unwrap();
+            quote!(if $test { $rhs } else)
         });
     }
 
diff --git a/src/test/run-pass-fulldeps/auxiliary/hello_macro.rs b/src/test/run-pass-fulldeps/auxiliary/hello_macro.rs
index 91075276a30..9522592a5e9 100644
--- a/src/test/run-pass-fulldeps/auxiliary/hello_macro.rs
+++ b/src/test/run-pass-fulldeps/auxiliary/hello_macro.rs
@@ -29,6 +29,11 @@ pub fn plugin_registrar(reg: &mut Registry) {
 
 // This macro is not very interesting, but it does contain delimited tokens with
 // no content - `()` and `{}` - which has caused problems in the past.
+// Also, it tests that we can escape `$` via `$$`.
 fn hello(_: TokenStream) -> TokenStream {
-    qquote!({ fn hello() {} hello(); })
+    quote!({
+        fn hello() {}
+        macro_rules! m { ($$($$t:tt)*) => { $$($$t)* } }
+        m!(hello());
+    })
 }
diff --git a/src/test/run-pass-fulldeps/auxiliary/proc_macro_def.rs b/src/test/run-pass-fulldeps/auxiliary/proc_macro_def.rs
index 612c199e828..0e37a7a5dcc 100644
--- a/src/test/run-pass-fulldeps/auxiliary/proc_macro_def.rs
+++ b/src/test/run-pass-fulldeps/auxiliary/proc_macro_def.rs
@@ -34,21 +34,21 @@ pub fn plugin_registrar(reg: &mut Registry) {
 }
 
 fn attr_tru(_attr: TokenStream, _item: TokenStream) -> TokenStream {
-    qquote!(fn f1() -> bool { true })
+    quote!(fn f1() -> bool { true })
 }
 
 fn attr_identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
-    qquote!(unquote item)
+    quote!($item)
 }
 
 fn tru(_ts: TokenStream) -> TokenStream {
-    qquote!(true)
+    quote!(true)
 }
 
 fn ret_tru(_ts: TokenStream) -> TokenStream {
-    qquote!(return true;)
+    quote!(return true;)
 }
 
 fn identity(ts: TokenStream) -> TokenStream {
-    qquote!(unquote ts)
+    quote!($ts)
 }
diff --git a/src/test/run-pass-fulldeps/macro-quote-1.rs b/src/test/run-pass-fulldeps/macro-quote-1.rs
index 57b6c3f0adb..01b0ed80235 100644
--- a/src/test/run-pass-fulldeps/macro-quote-1.rs
+++ b/src/test/run-pass-fulldeps/macro-quote-1.rs
@@ -22,6 +22,6 @@ use syntax::parse::token;
 use syntax::tokenstream::TokenTree;
 
 fn main() {
-    let true_tok = TokenTree::Token(syntax_pos::DUMMY_SP, token::Ident(Ident::from_str("true")));
-    assert!(qquote!(true).eq_unspanned(&true_tok.into()));
+    let true_tok = token::Ident(Ident::from_str("true"));
+    assert!(quote!(true).eq_unspanned(&true_tok.into()));
 }
diff --git a/src/test/run-pass-fulldeps/macro-quote-empty-delims.rs b/src/test/run-pass-fulldeps/macro-quote-test.rs
index bdbea8a4194..bdbea8a4194 100644
--- a/src/test/run-pass-fulldeps/macro-quote-empty-delims.rs
+++ b/src/test/run-pass-fulldeps/macro-quote-test.rs