1//! # Quasiquoter
2//! This file contains the implementation internals of the quasiquoter provided by `quote!`.
3
4//! This quasiquoter uses macros 2.0 hygiene to reliably access
5//! items from `proc_macro`, to build a `proc_macro::TokenStream`.
6
7use crate::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
8
9macro_rules! quote_tt {
10 (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, quote!($($t)*)) };
11 ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, quote!($($t)*)) };
12 ({$($t:tt)*}) => { Group::new(Delimiter::Brace, quote!($($t)*)) };
13 (,) => { Punct::new(',', Spacing::Alone) };
14 (.) => { Punct::new('.', Spacing::Alone) };
15 (;) => { Punct::new(';', Spacing::Alone) };
16 (!) => { Punct::new('!', Spacing::Alone) };
17 (<) => { Punct::new('<', Spacing::Alone) };
18 (>) => { Punct::new('>', Spacing::Alone) };
19 (&) => { Punct::new('&', Spacing::Alone) };
20 (=) => { Punct::new('=', Spacing::Alone) };
21 ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
22}
23
24macro_rules! quote_ts {
25 ((@ $($t:tt)*)) => { $($t)* };
26 (::) => {
27 [
28 TokenTree::from(Punct::new(':', Spacing::Joint)),
29 TokenTree::from(Punct::new(':', Spacing::Alone)),
30 ].iter()
31 .cloned()
32 .map(|mut x| {
33 x.set_span(Span::def_site());
34 x
35 })
36 .collect::<TokenStream>()
37 };
38 ($t:tt) => { TokenTree::from(quote_tt!($t)) };
39}
40
41/// Simpler version of the real `quote!` macro, implemented solely
42/// through `macro_rules`, for bootstrapping the real implementation
43/// (see the `quote` function), which does not have access to the
44/// real `quote!` macro due to the `proc_macro` crate not being
45/// able to depend on itself.
46///
47/// Note: supported tokens are a subset of the real `quote!`, but
48/// unquoting is different: instead of `$x`, this uses `(@ expr)`.
49macro_rules! quote {
50 () => { TokenStream::new() };
51 ($($t:tt)*) => {
52 [
53 $(TokenStream::from(quote_ts!($t)),)*
54 ].iter().cloned().collect::<TokenStream>()
55 };
56}
57
58/// Quote a `TokenStream` into a `TokenStream`.
59/// This is the actual implementation of the `quote!()` proc macro.
60///
61/// It is loaded by the compiler in `register_builtin_macros`.
62#[unstable(feature = "proc_macro_quote", issue = "54722")]
63pub fn quote(stream: TokenStream) -> TokenStream {
64 if stream.is_empty() {
65 return quote!(crate::TokenStream::new());
66 }
67 let proc_macro_crate = quote!(crate);
68 let mut after_dollar = false;
69 let tokens = stream
70 .into_iter()
71 .filter_map(|tree| {
72 if after_dollar {
73 after_dollar = false;
74 match tree {
75 TokenTree::Ident(_) => {
76 return Some(quote!(Into::<crate::TokenStream>::into(
77 Clone::clone(&(@ tree))),));
78 }
79 TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
80 _ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
81 }
82 } else if let TokenTree::Punct(ref tt) = tree {
83 if tt.as_char() == '$' {
84 after_dollar = true;
85 return None;
86 }
87 }
88
89 Some(quote!(crate::TokenStream::from((@ match tree {
90 TokenTree::Punct(tt) => quote!(crate::TokenTree::Punct(crate::Punct::new(
91 (@ TokenTree::from(Literal::character(tt.as_char()))),
92 (@ match tt.spacing() {
93 Spacing::Alone => quote!(crate::Spacing::Alone),
94 Spacing::Joint => quote!(crate::Spacing::Joint),
95 }),
96 ))),
97 TokenTree::Group(tt) => quote!(crate::TokenTree::Group(crate::Group::new(
98 (@ match tt.delimiter() {
99 Delimiter::Parenthesis => quote!(crate::Delimiter::Parenthesis),
100 Delimiter::Brace => quote!(crate::Delimiter::Brace),
101 Delimiter::Bracket => quote!(crate::Delimiter::Bracket),
102 Delimiter::None => quote!(crate::Delimiter::None),
103 }),
104 (@ quote(tt.stream())),
105 ))),
106 TokenTree::Ident(tt) => quote!(crate::TokenTree::Ident(crate::Ident::new(
107 (@ TokenTree::from(Literal::string(&tt.to_string()))),
108 (@ quote_span(proc_macro_crate.clone(), tt.span())),
109 ))),
110 TokenTree::Literal(tt) => quote!(crate::TokenTree::Literal({
111 let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string())))
112 .parse::<crate::TokenStream>()
113 .unwrap()
114 .into_iter();
115 if let (Some(crate::TokenTree::Literal(mut lit)), None) =
116 (iter.next(), iter.next())
117 {
118 lit.set_span((@ quote_span(proc_macro_crate.clone(), tt.span())));
119 lit
120 } else {
121 unreachable!()
122 }
123 }))
124 })),))
125 })
126 .collect::<TokenStream>();
127
128 if after_dollar {
129 panic!("unexpected trailing `$` in `quote!`");
130 }
131
132 quote!([(@ tokens)].iter().cloned().collect::<crate::TokenStream>())
133}
134
135/// Quote a `Span` into a `TokenStream`.
136/// This is needed to implement a custom quoter.
137#[unstable(feature = "proc_macro_quote", issue = "54722")]
138pub fn quote_span(proc_macro_crate: TokenStream, span: Span) -> TokenStream {
139 let id: usize = span.save_span();
140 quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id)))))
141}
142