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 | |
7 | use crate::{ |
8 | Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree, |
9 | }; |
10 | |
11 | macro_rules! minimal_quote_tt { |
12 | (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, minimal_quote!($($t)*)) }; |
13 | ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, minimal_quote!($($t)*)) }; |
14 | ({$($t:tt)*}) => { Group::new(Delimiter::Brace, minimal_quote!($($t)*)) }; |
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 | (&) => { Punct::new('&' , Spacing::Alone) }; |
22 | (=) => { Punct::new('=' , Spacing::Alone) }; |
23 | ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) }; |
24 | } |
25 | |
26 | macro_rules! minimal_quote_ts { |
27 | ((@ $($t:tt)*)) => { $($t)* }; |
28 | (::) => { |
29 | { |
30 | let mut c = ( |
31 | TokenTree::from(Punct::new(':' , Spacing::Joint)), |
32 | TokenTree::from(Punct::new(':' , Spacing::Alone)) |
33 | ); |
34 | c.0.set_span(Span::def_site()); |
35 | c.1.set_span(Span::def_site()); |
36 | [c.0, c.1].into_iter().collect::<TokenStream>() |
37 | } |
38 | }; |
39 | ($t:tt) => { TokenTree::from(minimal_quote_tt!($t)) }; |
40 | } |
41 | |
42 | /// Simpler version of the real `quote!` macro, implemented solely |
43 | /// through `macro_rules`, for bootstrapping the real implementation |
44 | /// (see the `quote` function), which does not have access to the |
45 | /// real `quote!` macro due to the `proc_macro` crate not being |
46 | /// able to depend on itself. |
47 | /// |
48 | /// Note: supported tokens are a subset of the real `quote!`, but |
49 | /// unquoting is different: instead of `$x`, this uses `(@ expr)`. |
50 | macro_rules! minimal_quote { |
51 | ($($t:tt)*) => { |
52 | { |
53 | #[allow(unused_mut)] // In case the expansion is empty |
54 | let mut ts = TokenStream::new(); |
55 | $(ToTokens::to_tokens(&minimal_quote_ts!($t), &mut ts);)* |
56 | ts |
57 | } |
58 | }; |
59 | } |
60 | |
61 | /// Quote a `TokenStream` into a `TokenStream`. |
62 | /// This is the actual implementation of the `quote!()` proc macro. |
63 | /// |
64 | /// It is loaded by the compiler in `register_builtin_macros`. |
65 | #[unstable (feature = "proc_macro_quote" , issue = "54722" )] |
66 | pub fn quote(stream: TokenStream) -> TokenStream { |
67 | if stream.is_empty() { |
68 | return minimal_quote!(crate::TokenStream::new()); |
69 | } |
70 | let proc_macro_crate = minimal_quote!(crate); |
71 | let mut after_dollar = false; |
72 | |
73 | let mut tokens = crate::TokenStream::new(); |
74 | for tree in stream { |
75 | if after_dollar { |
76 | after_dollar = false; |
77 | match tree { |
78 | TokenTree::Ident(_) => { |
79 | minimal_quote!(crate::ToTokens::to_tokens(&(@ tree), &mut ts);) |
80 | .to_tokens(&mut tokens); |
81 | continue; |
82 | } |
83 | TokenTree::Punct(ref tt) if tt.as_char() == '$' => {} |
84 | _ => panic!("`$` must be followed by an ident or `$` in `quote!`" ), |
85 | } |
86 | } else if let TokenTree::Punct(ref tt) = tree { |
87 | if tt.as_char() == '$' { |
88 | after_dollar = true; |
89 | continue; |
90 | } |
91 | } |
92 | |
93 | match tree { |
94 | TokenTree::Punct(tt) => { |
95 | minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new( |
96 | (@ TokenTree::from(Literal::character(tt.as_char()))), |
97 | (@ match tt.spacing() { |
98 | Spacing::Alone => minimal_quote!(crate::Spacing::Alone), |
99 | Spacing::Joint => minimal_quote!(crate::Spacing::Joint), |
100 | }), |
101 | )), &mut ts);) |
102 | } |
103 | TokenTree::Group(tt) => { |
104 | minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Group(crate::Group::new( |
105 | (@ match tt.delimiter() { |
106 | Delimiter::Parenthesis => minimal_quote!(crate::Delimiter::Parenthesis), |
107 | Delimiter::Brace => minimal_quote!(crate::Delimiter::Brace), |
108 | Delimiter::Bracket => minimal_quote!(crate::Delimiter::Bracket), |
109 | Delimiter::None => minimal_quote!(crate::Delimiter::None), |
110 | }), |
111 | (@ quote(tt.stream())), |
112 | )), &mut ts);) |
113 | } |
114 | TokenTree::Ident(tt) => { |
115 | let literal = tt.to_string(); |
116 | let (literal, ctor) = if let Some(stripped) = literal.strip_prefix("r#" ) { |
117 | (stripped, minimal_quote!(crate::Ident::new_raw)) |
118 | } else { |
119 | (literal.as_str(), minimal_quote!(crate::Ident::new)) |
120 | }; |
121 | minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Ident((@ ctor)( |
122 | (@ TokenTree::from(Literal::string(literal))), |
123 | (@ quote_span(proc_macro_crate.clone(), tt.span())), |
124 | )), &mut ts);) |
125 | } |
126 | TokenTree::Literal(tt) => { |
127 | minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Literal({ |
128 | let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string()))) |
129 | .parse::<crate::TokenStream>() |
130 | .unwrap() |
131 | .into_iter(); |
132 | if let (Some(crate::TokenTree::Literal(mut lit)), None) = |
133 | (iter.next(), iter.next()) |
134 | { |
135 | lit.set_span((@ quote_span(proc_macro_crate.clone(), tt.span()))); |
136 | lit |
137 | } else { |
138 | unreachable!() |
139 | } |
140 | }), &mut ts);) |
141 | } |
142 | } |
143 | .to_tokens(&mut tokens); |
144 | } |
145 | if after_dollar { |
146 | panic!("unexpected trailing `$` in `quote!`" ); |
147 | } |
148 | |
149 | minimal_quote! { |
150 | { |
151 | let mut ts = crate::TokenStream::new(); |
152 | (@ tokens) |
153 | ts |
154 | } |
155 | } |
156 | } |
157 | |
158 | /// Quote a `Span` into a `TokenStream`. |
159 | /// This is needed to implement a custom quoter. |
160 | #[unstable (feature = "proc_macro_quote" , issue = "54722" )] |
161 | pub fn quote_span(proc_macro_crate: TokenStream, span: Span) -> TokenStream { |
162 | let id: usize = span.save_span(); |
163 | minimal_quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id))))) |
164 | } |
165 | |