Rollup merge of #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 #39746), and - to preserve syntactic space, requires that `$` be followed by - a single identifier to unquote, or - another `$` to produce a literal `$`. r? @nrc
This commit is contained in:
commit
7471d9793c
@ -13,62 +13,64 @@
|
|||||||
//! A library for procedural macro writers.
|
//! A library for procedural macro writers.
|
||||||
//!
|
//!
|
||||||
//! ## Usage
|
//! ## 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.
|
//! at the crate root. This is a temporary solution until we have better hygiene.
|
||||||
//!
|
//!
|
||||||
//! ## Quasiquotation
|
//! ## Quasiquotation
|
||||||
//!
|
//!
|
||||||
//! The quasiquoter creates output that, when run, constructs the tokenstream specified as
|
//! 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`.
|
//! construct the TokenStream `5 | + | 5`.
|
||||||
//!
|
//!
|
||||||
//! ### Unquoting
|
//! ### Unquoting
|
||||||
//!
|
//!
|
||||||
//! Unquoting is currently done as `unquote`, and works by taking the single next
|
//! Unquoting is done with `$`, and works by taking the single next ident as the unquoted term.
|
||||||
//! TokenTree in the TokenStream as the unquoted term. Ergonomically, `unquote(foo)` works
|
//! To quote `$` itself, use `$$`.
|
||||||
//! fine, but `unquote foo` is also supported.
|
|
||||||
//!
|
//!
|
||||||
//! A simple example might be:
|
//! A simple example is:
|
||||||
//!
|
//!
|
||||||
//!```
|
//!```
|
||||||
//!fn double(tmp: TokenStream) -> TokenStream {
|
//!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 {
|
//! fn cond(input: TokenStream) -> TokenStream {
|
||||||
//! if input.is_empty() { return quote!(); }
|
//! let mut conds = Vec::new();
|
||||||
//!
|
//! let mut input = input.trees().peekable();
|
||||||
//! let next = input.slice(0..1);
|
//! while let Some(tree) = input.next() {
|
||||||
//! let rest = input.slice_from(1..);
|
//! let mut cond = match tree {
|
||||||
//!
|
//! TokenTree::Delimited(_, ref delimited) => delimited.stream(),
|
||||||
//! let clause : TokenStream = match next.maybe_delimited() {
|
|
||||||
//! Some(ts) => ts,
|
|
||||||
//! _ => panic!("Invalid input"),
|
//! _ => panic!("Invalid input"),
|
||||||
//! };
|
//! };
|
||||||
//!
|
//! let mut trees = cond.trees();
|
||||||
//! // clause is ([test]) [rhs]
|
//! let test = trees.next();
|
||||||
//! if clause.len() < 2 { panic!("Invalid macro usage in cond: {:?}", clause) }
|
//! let rhs = trees.collect::<TokenStream>();
|
||||||
//!
|
//! if rhs.is_empty() {
|
||||||
//! let test: TokenStream = clause.slice(0..1);
|
//! panic!("Invalid macro usage in cond: {}", cond);
|
||||||
//! 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)) } })
|
|
||||||
//! }
|
//! }
|
||||||
|
//! 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"]
|
#![crate_name = "proc_macro_plugin"]
|
||||||
#![unstable(feature = "rustc_private", issue = "27812")]
|
#![unstable(feature = "rustc_private", issue = "27812")]
|
||||||
#![feature(plugin_registrar)]
|
#![feature(plugin_registrar)]
|
||||||
@ -87,8 +89,8 @@ extern crate rustc_plugin;
|
|||||||
extern crate syntax;
|
extern crate syntax;
|
||||||
extern crate syntax_pos;
|
extern crate syntax_pos;
|
||||||
|
|
||||||
mod qquote;
|
mod quote;
|
||||||
use qquote::qquote;
|
use quote::quote;
|
||||||
|
|
||||||
use rustc_plugin::Registry;
|
use rustc_plugin::Registry;
|
||||||
use syntax::ext::base::SyntaxExtension;
|
use syntax::ext::base::SyntaxExtension;
|
||||||
@ -99,6 +101,6 @@ use syntax::symbol::Symbol;
|
|||||||
|
|
||||||
#[plugin_registrar]
|
#[plugin_registrar]
|
||||||
pub fn plugin_registrar(reg: &mut Registry) {
|
pub fn plugin_registrar(reg: &mut Registry) {
|
||||||
reg.register_syntax_extension(Symbol::intern("qquote"),
|
reg.register_syntax_extension(Symbol::intern("quote"),
|
||||||
SyntaxExtension::ProcMacro(Box::new(qquote)));
|
SyntaxExtension::ProcMacro(Box::new(quote)));
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ use syntax_pos::DUMMY_SP;
|
|||||||
|
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
pub fn qquote<'cx>(stream: TokenStream) -> TokenStream {
|
pub fn quote<'cx>(stream: TokenStream) -> TokenStream {
|
||||||
stream.quote()
|
stream.quote()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,28 +72,32 @@ impl Quote for TokenStream {
|
|||||||
return quote!(::syntax::tokenstream::TokenStream::empty());
|
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;
|
type Item = TokenStream;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<TokenStream> {
|
fn next(&mut self) -> Option<TokenStream> {
|
||||||
let is_unquote = match self.0.peek() {
|
let quoted_tree = if let Some(&TokenTree::Token(_, Token::Dollar)) = self.0.peek() {
|
||||||
Some(&TokenTree::Token(_, Token::Ident(ident))) if ident.name == "unquote" => {
|
|
||||||
self.0.next();
|
self.0.next();
|
||||||
true
|
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| {
|
quoted_tree.map(|quoted_tree| {
|
||||||
let quoted_tree = if is_unquote { tree.into() } else { tree.quote() };
|
|
||||||
quote!(::syntax::tokenstream::TokenStream::from((unquote 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>())
|
quote!([(unquote quoted)].iter().cloned().collect::<::syntax::tokenstream::TokenStream>())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 {
|
impl<T: Into<TokenStream>> iter::FromIterator<T> for TokenStream {
|
||||||
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||||
TokenStream::concat(iter.into_iter().map(Into::into).collect::<Vec<_>>())
|
TokenStream::concat(iter.into_iter().map(Into::into).collect::<Vec<_>>())
|
||||||
|
@ -49,9 +49,10 @@ fn cond(input: TokenStream) -> TokenStream {
|
|||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
conds.push(if is_else || input.peek().is_none() {
|
conds.push(if is_else || input.peek().is_none() {
|
||||||
qquote!({ unquote rhs })
|
quote!({ $rhs })
|
||||||
} else {
|
} else {
|
||||||
qquote!(if unquote(test.unwrap()) { unquote rhs } else)
|
let test = test.unwrap();
|
||||||
|
quote!(if $test { $rhs } else)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,11 @@ pub fn plugin_registrar(reg: &mut Registry) {
|
|||||||
|
|
||||||
// This macro is not very interesting, but it does contain delimited tokens with
|
// This macro is not very interesting, but it does contain delimited tokens with
|
||||||
// no content - `()` and `{}` - which has caused problems in the past.
|
// no content - `()` and `{}` - which has caused problems in the past.
|
||||||
|
// Also, it tests that we can escape `$` via `$$`.
|
||||||
fn hello(_: TokenStream) -> TokenStream {
|
fn hello(_: TokenStream) -> TokenStream {
|
||||||
qquote!({ fn hello() {} hello(); })
|
quote!({
|
||||||
|
fn hello() {}
|
||||||
|
macro_rules! m { ($$($$t:tt)*) => { $$($$t)* } }
|
||||||
|
m!(hello());
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -34,21 +34,21 @@ pub fn plugin_registrar(reg: &mut Registry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn attr_tru(_attr: TokenStream, _item: TokenStream) -> TokenStream {
|
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 {
|
fn attr_identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
qquote!(unquote item)
|
quote!($item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tru(_ts: TokenStream) -> TokenStream {
|
fn tru(_ts: TokenStream) -> TokenStream {
|
||||||
qquote!(true)
|
quote!(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ret_tru(_ts: TokenStream) -> TokenStream {
|
fn ret_tru(_ts: TokenStream) -> TokenStream {
|
||||||
qquote!(return true;)
|
quote!(return true;)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identity(ts: TokenStream) -> TokenStream {
|
fn identity(ts: TokenStream) -> TokenStream {
|
||||||
qquote!(unquote ts)
|
quote!($ts)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,6 @@ use syntax::parse::token;
|
|||||||
use syntax::tokenstream::TokenTree;
|
use syntax::tokenstream::TokenTree;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let true_tok = TokenTree::Token(syntax_pos::DUMMY_SP, token::Ident(Ident::from_str("true")));
|
let true_tok = token::Ident(Ident::from_str("true"));
|
||||||
assert!(qquote!(true).eq_unspanned(&true_tok.into()));
|
assert!(quote!(true).eq_unspanned(&true_tok.into()));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user