Skip to content

Commit 663409b

Browse files
author
Ariel Ben-Yehuda
authored
Rollup merge of rust-lang#40532 - jseyfried:improve_tokenstream_quoter, r=nrc
macros: improve the `TokenStream` quoter This PR - renames the `TokenStream` quoter from `qquote!` to `quote!`, - uses `$` instead of `unquote` (e.g. `let toks: TokenStream = ...; quote!([$toks])`), - allows unquoting `Token`s as well as `TokenTree`s and `TokenStream`s (fixes rust-lang#39746), and - to preserve syntactic space, requires that `$` be followed by - a single identifier to unquote, or - another `$` to produce a literal `$`. r? @nrc
2 parents ca26c79 + ce616a7 commit 663409b

File tree

8 files changed

+77
-59
lines changed

8 files changed

+77
-59
lines changed

src/libproc_macro_plugin/lib.rs

+40-38
Original file line numberDiff line numberDiff line change
@@ -13,62 +13,64 @@
1313
//! A library for procedural macro writers.
1414
//!
1515
//! ## Usage
16-
//! This crate provides the `qquote!` macro for syntax creation.
16+
//! This crate provides the `quote!` macro for syntax creation.
1717
//!
18-
//! The `qquote!` macro uses the crate `syntax`, so users must declare `extern crate syntax;`
18+
//! The `quote!` macro uses the crate `syntax`, so users must declare `extern crate syntax;`
1919
//! at the crate root. This is a temporary solution until we have better hygiene.
2020
//!
2121
//! ## Quasiquotation
2222
//!
2323
//! The quasiquoter creates output that, when run, constructs the tokenstream specified as
24-
//! input. For example, `qquote!(5 + 5)` will produce a program, that, when run, will
24+
//! input. For example, `quote!(5 + 5)` will produce a program, that, when run, will
2525
//! construct the TokenStream `5 | + | 5`.
2626
//!
2727
//! ### Unquoting
2828
//!
29-
//! Unquoting is currently done as `unquote`, and works by taking the single next
30-
//! TokenTree in the TokenStream as the unquoted term. Ergonomically, `unquote(foo)` works
31-
//! fine, but `unquote foo` is also supported.
29+
//! Unquoting is done with `$`, and works by taking the single next ident as the unquoted term.
30+
//! To quote `$` itself, use `$$`.
3231
//!
33-
//! A simple example might be:
32+
//! A simple example is:
3433
//!
3534
//!```
3635
//!fn double(tmp: TokenStream) -> TokenStream {
37-
//! qquote!(unquote(tmp) * 2)
36+
//! quote!($tmp * 2)
3837
//!}
3938
//!```
4039
//!
41-
//! ### Large Example: Implementing Scheme's `cond`
40+
//! ### Large example: Scheme's `cond`
4241
//!
43-
//! Below is the full implementation of Scheme's `cond` operator.
42+
//! Below is an example implementation of Scheme's `cond`.
4443
//!
4544
//! ```
46-
//! fn cond_rec(input: TokenStream) -> TokenStream {
47-
//! if input.is_empty() { return quote!(); }
48-
//!
49-
//! let next = input.slice(0..1);
50-
//! let rest = input.slice_from(1..);
51-
//!
52-
//! let clause : TokenStream = match next.maybe_delimited() {
53-
//! Some(ts) => ts,
54-
//! _ => panic!("Invalid input"),
55-
//! };
56-
//!
57-
//! // clause is ([test]) [rhs]
58-
//! if clause.len() < 2 { panic!("Invalid macro usage in cond: {:?}", clause) }
59-
//!
60-
//! let test: TokenStream = clause.slice(0..1);
61-
//! let rhs: TokenStream = clause.slice_from(1..);
62-
//!
63-
//! if ident_eq(&test[0], str_to_ident("else")) || rest.is_empty() {
64-
//! quote!({unquote(rhs)})
65-
//! } else {
66-
//! quote!({if unquote(test) { unquote(rhs) } else { cond!(unquote(rest)) } })
67-
//! }
45+
//! fn cond(input: TokenStream) -> TokenStream {
46+
//! let mut conds = Vec::new();
47+
//! let mut input = input.trees().peekable();
48+
//! while let Some(tree) = input.next() {
49+
//! let mut cond = match tree {
50+
//! TokenTree::Delimited(_, ref delimited) => delimited.stream(),
51+
//! _ => panic!("Invalid input"),
52+
//! };
53+
//! let mut trees = cond.trees();
54+
//! let test = trees.next();
55+
//! let rhs = trees.collect::<TokenStream>();
56+
//! if rhs.is_empty() {
57+
//! panic!("Invalid macro usage in cond: {}", cond);
58+
//! }
59+
//! let is_else = match test {
60+
//! Some(TokenTree::Token(_, Token::Ident(ident))) if ident.name == "else" => true,
61+
//! _ => false,
62+
//! };
63+
//! conds.push(if is_else || input.peek().is_none() {
64+
//! quote!({ $rhs })
65+
//! } else {
66+
//! let test = test.unwrap();
67+
//! quote!(if $test { $rhs } else)
68+
//! });
69+
//! }
70+
//!
71+
//! conds.into_iter().collect()
6872
//! }
6973
//! ```
70-
//!
71-
7274
#![crate_name = "proc_macro_plugin"]
7375
#![unstable(feature = "rustc_private", issue = "27812")]
7476
#![feature(plugin_registrar)]
@@ -87,8 +89,8 @@ extern crate rustc_plugin;
8789
extern crate syntax;
8890
extern crate syntax_pos;
8991

90-
mod qquote;
91-
use qquote::qquote;
92+
mod quote;
93+
use quote::quote;
9294

9395
use rustc_plugin::Registry;
9496
use syntax::ext::base::SyntaxExtension;
@@ -99,6 +101,6 @@ use syntax::symbol::Symbol;
99101

100102
#[plugin_registrar]
101103
pub fn plugin_registrar(reg: &mut Registry) {
102-
reg.register_syntax_extension(Symbol::intern("qquote"),
103-
SyntaxExtension::ProcMacro(Box::new(qquote)));
104+
reg.register_syntax_extension(Symbol::intern("quote"),
105+
SyntaxExtension::ProcMacro(Box::new(quote)));
104106
}

src/libproc_macro_plugin/qquote.rs src/libproc_macro_plugin/quote.rs

+15-11
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use syntax_pos::DUMMY_SP;
1919

2020
use std::iter;
2121

22-
pub fn qquote<'cx>(stream: TokenStream) -> TokenStream {
22+
pub fn quote<'cx>(stream: TokenStream) -> TokenStream {
2323
stream.quote()
2424
}
2525

@@ -72,28 +72,32 @@ impl Quote for TokenStream {
7272
return quote!(::syntax::tokenstream::TokenStream::empty());
7373
}
7474

75-
struct Quote(iter::Peekable<tokenstream::Cursor>);
75+
struct Quoter(iter::Peekable<tokenstream::Cursor>);
7676

77-
impl Iterator for Quote {
77+
impl Iterator for Quoter {
7878
type Item = TokenStream;
7979

8080
fn next(&mut self) -> Option<TokenStream> {
81-
let is_unquote = match self.0.peek() {
82-
Some(&TokenTree::Token(_, Token::Ident(ident))) if ident.name == "unquote" => {
83-
self.0.next();
84-
true
81+
let quoted_tree = if let Some(&TokenTree::Token(_, Token::Dollar)) = self.0.peek() {
82+
self.0.next();
83+
match self.0.next() {
84+
Some(tree @ TokenTree::Token(_, Token::Ident(..))) => Some(tree.into()),
85+
Some(tree @ TokenTree::Token(_, Token::Dollar)) => Some(tree.quote()),
86+
// FIXME(jseyfried): improve these diagnostics
87+
Some(..) => panic!("`$` must be followed by an ident or `$` in `quote!`"),
88+
None => panic!("unexpected trailing `$` in `quote!`"),
8589
}
86-
_ => false,
90+
} else {
91+
self.0.next().as_ref().map(Quote::quote)
8792
};
8893

89-
self.0.next().map(|tree| {
90-
let quoted_tree = if is_unquote { tree.into() } else { tree.quote() };
94+
quoted_tree.map(|quoted_tree| {
9195
quote!(::syntax::tokenstream::TokenStream::from((unquote quoted_tree)),)
9296
})
9397
}
9498
}
9599

96-
let quoted = Quote(self.trees().peekable()).collect::<TokenStream>();
100+
let quoted = Quoter(self.trees().peekable()).collect::<TokenStream>();
97101
quote!([(unquote quoted)].iter().cloned().collect::<::syntax::tokenstream::TokenStream>())
98102
}
99103
}

src/libsyntax/tokenstream.rs

+6
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ impl From<TokenTree> for TokenStream {
162162
}
163163
}
164164

165+
impl From<Token> for TokenStream {
166+
fn from(token: Token) -> TokenStream {
167+
TokenTree::Token(DUMMY_SP, token).into()
168+
}
169+
}
170+
165171
impl<T: Into<TokenStream>> iter::FromIterator<T> for TokenStream {
166172
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
167173
TokenStream::concat(iter.into_iter().map(Into::into).collect::<Vec<_>>())

src/test/run-pass-fulldeps/auxiliary/cond_plugin.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ fn cond(input: TokenStream) -> TokenStream {
4949
_ => false,
5050
};
5151
conds.push(if is_else || input.peek().is_none() {
52-
qquote!({ unquote rhs })
52+
quote!({ $rhs })
5353
} else {
54-
qquote!(if unquote(test.unwrap()) { unquote rhs } else)
54+
let test = test.unwrap();
55+
quote!(if $test { $rhs } else)
5556
});
5657
}
5758

src/test/run-pass-fulldeps/auxiliary/hello_macro.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ pub fn plugin_registrar(reg: &mut Registry) {
2929

3030
// This macro is not very interesting, but it does contain delimited tokens with
3131
// no content - `()` and `{}` - which has caused problems in the past.
32+
// Also, it tests that we can escape `$` via `$$`.
3233
fn hello(_: TokenStream) -> TokenStream {
33-
qquote!({ fn hello() {} hello(); })
34+
quote!({
35+
fn hello() {}
36+
macro_rules! m { ($$($$t:tt)*) => { $$($$t)* } }
37+
m!(hello());
38+
})
3439
}

src/test/run-pass-fulldeps/auxiliary/proc_macro_def.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,21 @@ pub fn plugin_registrar(reg: &mut Registry) {
3434
}
3535

3636
fn attr_tru(_attr: TokenStream, _item: TokenStream) -> TokenStream {
37-
qquote!(fn f1() -> bool { true })
37+
quote!(fn f1() -> bool { true })
3838
}
3939

4040
fn attr_identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
41-
qquote!(unquote item)
41+
quote!($item)
4242
}
4343

4444
fn tru(_ts: TokenStream) -> TokenStream {
45-
qquote!(true)
45+
quote!(true)
4646
}
4747

4848
fn ret_tru(_ts: TokenStream) -> TokenStream {
49-
qquote!(return true;)
49+
quote!(return true;)
5050
}
5151

5252
fn identity(ts: TokenStream) -> TokenStream {
53-
qquote!(unquote ts)
53+
quote!($ts)
5454
}

src/test/run-pass-fulldeps/macro-quote-1.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ use syntax::parse::token;
2222
use syntax::tokenstream::TokenTree;
2323

2424
fn main() {
25-
let true_tok = TokenTree::Token(syntax_pos::DUMMY_SP, token::Ident(Ident::from_str("true")));
26-
assert!(qquote!(true).eq_unspanned(&true_tok.into()));
25+
let true_tok = token::Ident(Ident::from_str("true"));
26+
assert!(quote!(true).eq_unspanned(&true_tok.into()));
2727
}

0 commit comments

Comments
 (0)