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::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; |
8 | |
9 | macro_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 | |
24 | macro_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)`. |
49 | macro_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" )] |
63 | pub 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" )] |
138 | pub 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 | |