diff options
Diffstat (limited to 'crates/hir_expand/src/quote.rs')
-rw-r--r-- | crates/hir_expand/src/quote.rs | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/crates/hir_expand/src/quote.rs b/crates/hir_expand/src/quote.rs new file mode 100644 index 000000000..219bc2097 --- /dev/null +++ b/crates/hir_expand/src/quote.rs | |||
@@ -0,0 +1,282 @@ | |||
1 | //! A simplified version of quote-crate like quasi quote macro | ||
2 | |||
3 | // A helper macro quote macro | ||
4 | // FIXME: | ||
5 | // 1. Not all puncts are handled | ||
6 | // 2. #()* pattern repetition not supported now | ||
7 | // * But we can do it manually, see `test_quote_derive_copy_hack` | ||
8 | #[doc(hidden)] | ||
9 | #[macro_export] | ||
10 | macro_rules! __quote { | ||
11 | () => { | ||
12 | Vec::<tt::TokenTree>::new() | ||
13 | }; | ||
14 | |||
15 | ( @SUBTREE $delim:ident $($tt:tt)* ) => { | ||
16 | { | ||
17 | let children = $crate::__quote!($($tt)*); | ||
18 | tt::Subtree { | ||
19 | delimiter: Some(tt::Delimiter { | ||
20 | kind: tt::DelimiterKind::$delim, | ||
21 | id: tt::TokenId::unspecified(), | ||
22 | }), | ||
23 | token_trees: $crate::quote::IntoTt::to_tokens(children), | ||
24 | } | ||
25 | } | ||
26 | }; | ||
27 | |||
28 | ( @PUNCT $first:literal ) => { | ||
29 | { | ||
30 | vec![ | ||
31 | tt::Leaf::Punct(tt::Punct { | ||
32 | char: $first, | ||
33 | spacing: tt::Spacing::Alone, | ||
34 | id: tt::TokenId::unspecified(), | ||
35 | }).into() | ||
36 | ] | ||
37 | } | ||
38 | }; | ||
39 | |||
40 | ( @PUNCT $first:literal, $sec:literal ) => { | ||
41 | { | ||
42 | vec![ | ||
43 | tt::Leaf::Punct(tt::Punct { | ||
44 | char: $first, | ||
45 | spacing: tt::Spacing::Joint, | ||
46 | id: tt::TokenId::unspecified(), | ||
47 | }).into(), | ||
48 | tt::Leaf::Punct(tt::Punct { | ||
49 | char: $sec, | ||
50 | spacing: tt::Spacing::Alone, | ||
51 | id: tt::TokenId::unspecified(), | ||
52 | }).into() | ||
53 | ] | ||
54 | } | ||
55 | }; | ||
56 | |||
57 | // hash variable | ||
58 | ( # $first:ident $($tail:tt)* ) => { | ||
59 | { | ||
60 | let token = $crate::quote::ToTokenTree::to_token($first); | ||
61 | let mut tokens = vec![token.into()]; | ||
62 | let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*)); | ||
63 | tokens.append(&mut tail_tokens); | ||
64 | tokens | ||
65 | } | ||
66 | }; | ||
67 | |||
68 | ( ## $first:ident $($tail:tt)* ) => { | ||
69 | { | ||
70 | let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::<Vec<tt::TokenTree>>(); | ||
71 | let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*)); | ||
72 | tokens.append(&mut tail_tokens); | ||
73 | tokens | ||
74 | } | ||
75 | }; | ||
76 | |||
77 | // Brace | ||
78 | ( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) }; | ||
79 | // Bracket | ||
80 | ( [ $($tt:tt)* ] ) => { $crate::__quote!(@SUBTREE Bracket $($tt)*) }; | ||
81 | // Parenthesis | ||
82 | ( ( $($tt:tt)* ) ) => { $crate::__quote!(@SUBTREE Parenthesis $($tt)*) }; | ||
83 | |||
84 | // Literal | ||
85 | ( $tt:literal ) => { vec![$crate::quote::ToTokenTree::to_token($tt).into()] }; | ||
86 | // Ident | ||
87 | ( $tt:ident ) => { | ||
88 | vec![ { | ||
89 | tt::Leaf::Ident(tt::Ident { | ||
90 | text: stringify!($tt).into(), | ||
91 | id: tt::TokenId::unspecified(), | ||
92 | }).into() | ||
93 | }] | ||
94 | }; | ||
95 | |||
96 | // Puncts | ||
97 | // FIXME: Not all puncts are handled | ||
98 | ( -> ) => {$crate::__quote!(@PUNCT '-', '>')}; | ||
99 | ( & ) => {$crate::__quote!(@PUNCT '&')}; | ||
100 | ( , ) => {$crate::__quote!(@PUNCT ',')}; | ||
101 | ( : ) => {$crate::__quote!(@PUNCT ':')}; | ||
102 | ( ; ) => {$crate::__quote!(@PUNCT ';')}; | ||
103 | ( :: ) => {$crate::__quote!(@PUNCT ':', ':')}; | ||
104 | ( . ) => {$crate::__quote!(@PUNCT '.')}; | ||
105 | ( < ) => {$crate::__quote!(@PUNCT '<')}; | ||
106 | ( > ) => {$crate::__quote!(@PUNCT '>')}; | ||
107 | |||
108 | ( $first:tt $($tail:tt)+ ) => { | ||
109 | { | ||
110 | let mut tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($first)); | ||
111 | let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*)); | ||
112 | |||
113 | tokens.append(&mut tail_tokens); | ||
114 | tokens | ||
115 | } | ||
116 | }; | ||
117 | } | ||
118 | |||
119 | /// FIXME: | ||
120 | /// It probably should implement in proc-macro | ||
121 | #[macro_export] | ||
122 | macro_rules! quote { | ||
123 | ( $($tt:tt)* ) => { | ||
124 | $crate::quote::IntoTt::to_subtree($crate::__quote!($($tt)*)) | ||
125 | } | ||
126 | } | ||
127 | |||
128 | pub(crate) trait IntoTt { | ||
129 | fn to_subtree(self) -> tt::Subtree; | ||
130 | fn to_tokens(self) -> Vec<tt::TokenTree>; | ||
131 | } | ||
132 | |||
133 | impl IntoTt for Vec<tt::TokenTree> { | ||
134 | fn to_subtree(self) -> tt::Subtree { | ||
135 | tt::Subtree { delimiter: None, token_trees: self } | ||
136 | } | ||
137 | |||
138 | fn to_tokens(self) -> Vec<tt::TokenTree> { | ||
139 | self | ||
140 | } | ||
141 | } | ||
142 | |||
143 | impl IntoTt for tt::Subtree { | ||
144 | fn to_subtree(self) -> tt::Subtree { | ||
145 | self | ||
146 | } | ||
147 | |||
148 | fn to_tokens(self) -> Vec<tt::TokenTree> { | ||
149 | vec![tt::TokenTree::Subtree(self)] | ||
150 | } | ||
151 | } | ||
152 | |||
153 | pub(crate) trait ToTokenTree { | ||
154 | fn to_token(self) -> tt::TokenTree; | ||
155 | } | ||
156 | |||
157 | impl ToTokenTree for tt::TokenTree { | ||
158 | fn to_token(self) -> tt::TokenTree { | ||
159 | self | ||
160 | } | ||
161 | } | ||
162 | |||
163 | impl ToTokenTree for tt::Subtree { | ||
164 | fn to_token(self) -> tt::TokenTree { | ||
165 | self.into() | ||
166 | } | ||
167 | } | ||
168 | |||
169 | macro_rules! impl_to_to_tokentrees { | ||
170 | ($($ty:ty => $this:ident $im:block);*) => { | ||
171 | $( | ||
172 | impl ToTokenTree for $ty { | ||
173 | fn to_token($this) -> tt::TokenTree { | ||
174 | let leaf: tt::Leaf = $im.into(); | ||
175 | leaf.into() | ||
176 | } | ||
177 | } | ||
178 | |||
179 | impl ToTokenTree for &$ty { | ||
180 | fn to_token($this) -> tt::TokenTree { | ||
181 | let leaf: tt::Leaf = $im.clone().into(); | ||
182 | leaf.into() | ||
183 | } | ||
184 | } | ||
185 | )* | ||
186 | } | ||
187 | } | ||
188 | |||
189 | impl_to_to_tokentrees! { | ||
190 | u32 => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()} }; | ||
191 | usize => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()}}; | ||
192 | i32 => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()}}; | ||
193 | tt::Leaf => self { self }; | ||
194 | tt::Literal => self { self }; | ||
195 | tt::Ident => self { self }; | ||
196 | tt::Punct => self { self }; | ||
197 | &str => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into(), id: tt::TokenId::unspecified()}}; | ||
198 | String => self { tt::Literal{text: format!("{:?}", self.escape_default().to_string()).into(), id: tt::TokenId::unspecified()}} | ||
199 | } | ||
200 | |||
201 | #[cfg(test)] | ||
202 | mod tests { | ||
203 | #[test] | ||
204 | fn test_quote_delimiters() { | ||
205 | assert_eq!(quote!({}).to_string(), "{}"); | ||
206 | assert_eq!(quote!(()).to_string(), "()"); | ||
207 | assert_eq!(quote!([]).to_string(), "[]"); | ||
208 | } | ||
209 | |||
210 | #[test] | ||
211 | fn test_quote_idents() { | ||
212 | assert_eq!(quote!(32).to_string(), "32"); | ||
213 | assert_eq!(quote!(struct).to_string(), "struct"); | ||
214 | } | ||
215 | |||
216 | #[test] | ||
217 | fn test_quote_hash_simple_literal() { | ||
218 | let a = 20; | ||
219 | assert_eq!(quote!(#a).to_string(), "20"); | ||
220 | let s: String = "hello".into(); | ||
221 | assert_eq!(quote!(#s).to_string(), "\"hello\""); | ||
222 | } | ||
223 | |||
224 | fn mk_ident(name: &str) -> tt::Ident { | ||
225 | tt::Ident { text: name.into(), id: tt::TokenId::unspecified() } | ||
226 | } | ||
227 | |||
228 | #[test] | ||
229 | fn test_quote_hash_token_tree() { | ||
230 | let a = mk_ident("hello"); | ||
231 | |||
232 | let quoted = quote!(#a); | ||
233 | assert_eq!(quoted.to_string(), "hello"); | ||
234 | let t = format!("{:?}", quoted); | ||
235 | assert_eq!(t, "SUBTREE $\n IDENT hello 4294967295"); | ||
236 | } | ||
237 | |||
238 | #[test] | ||
239 | fn test_quote_simple_derive_copy() { | ||
240 | let name = mk_ident("Foo"); | ||
241 | |||
242 | let quoted = quote! { | ||
243 | impl Clone for #name { | ||
244 | fn clone(&self) -> Self { | ||
245 | Self {} | ||
246 | } | ||
247 | } | ||
248 | }; | ||
249 | |||
250 | assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {}}}"); | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn test_quote_derive_copy_hack() { | ||
255 | // Assume the given struct is: | ||
256 | // struct Foo { | ||
257 | // name: String, | ||
258 | // id: u32, | ||
259 | // } | ||
260 | let struct_name = mk_ident("Foo"); | ||
261 | let fields = [mk_ident("name"), mk_ident("id")]; | ||
262 | let fields = fields.iter().map(|it| quote!(#it: self.#it.clone(), ).token_trees).flatten(); | ||
263 | |||
264 | let list = tt::Subtree { | ||
265 | delimiter: Some(tt::Delimiter { | ||
266 | kind: tt::DelimiterKind::Brace, | ||
267 | id: tt::TokenId::unspecified(), | ||
268 | }), | ||
269 | token_trees: fields.collect(), | ||
270 | }; | ||
271 | |||
272 | let quoted = quote! { | ||
273 | impl Clone for #struct_name { | ||
274 | fn clone(&self) -> Self { | ||
275 | Self #list | ||
276 | } | ||
277 | } | ||
278 | }; | ||
279 | |||
280 | assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {name : self . name . clone () , id : self . id . clone () ,}}}"); | ||
281 | } | ||
282 | } | ||