blob: 31224737263e9bd604e1322c1a63bc14b9a750ce [file] [log] [blame]
David Tolnayf1e57f02019-05-08 15:30:39 -07001use crate::file;
2use quote::quote;
3use syn_codegen as types;
4
5const FOLD_SRC: &str = "../src/gen/fold.rs";
6
7mod codegen {
8 use inflections::Inflect;
9 use proc_macro2::{Span, TokenStream};
10 use quote::{quote, TokenStreamExt};
11 use syn::*;
12 use syn_codegen as types;
13
14 #[derive(Default)]
15 pub struct State {
16 pub fold_trait: TokenStream,
17 pub fold_impl: TokenStream,
18 }
19
20 fn under_name(name: &str) -> Ident {
21 Ident::new(&name.to_snake_case(), Span::call_site())
22 }
23
24 fn simple_visit(item: &str, name: &TokenStream) -> TokenStream {
25 let ident = under_name(item);
26
27 let method = Ident::new(&format!("fold_{}", ident), Span::call_site());
28 quote! {
29 _visitor.#method(#name)
30 }
31 }
32
33 fn box_visit(
34 elem: &types::Type,
35 features: &types::Features,
36 defs: &types::Definitions,
37 name: &TokenStream,
38 ) -> Option<TokenStream> {
39 let res = visit(elem, features, defs, &quote!(*#name))?;
40 Some(quote! {
41 Box::new(#res)
42 })
43 }
44
45 fn vec_visit(
46 elem: &types::Type,
47 features: &types::Features,
48 defs: &types::Definitions,
49 name: &TokenStream,
50 ) -> Option<TokenStream> {
51 let operand = quote!(it);
52 let val = visit(elem, features, defs, &operand)?;
53 Some(quote! {
54 FoldHelper::lift(#name, |it| { #val })
55 })
56 }
57
58 fn punctuated_visit(
59 elem: &types::Type,
60 features: &types::Features,
61 defs: &types::Definitions,
62 name: &TokenStream,
63 ) -> Option<TokenStream> {
64 let operand = quote!(it);
65 let val = visit(elem, features, defs, &operand)?;
66 Some(quote! {
67 FoldHelper::lift(#name, |it| { #val })
68 })
69 }
70
71 fn option_visit(
72 elem: &types::Type,
73 features: &types::Features,
74 defs: &types::Definitions,
75 name: &TokenStream,
76 ) -> Option<TokenStream> {
77 let it = quote!(it);
78 let val = visit(elem, features, defs, &it)?;
79 Some(quote! {
80 (#name).map(|it| { #val })
81 })
82 }
83
84 fn tuple_visit(
85 elems: &[types::Type],
86 features: &types::Features,
87 defs: &types::Definitions,
88 name: &TokenStream,
89 ) -> Option<TokenStream> {
90 if elems.is_empty() {
91 return None;
92 }
93
94 let mut code = TokenStream::new();
95 for (i, elem) in elems.iter().enumerate() {
96 let i = Index::from(i);
97 let it = quote!((#name).#i);
98 let val = visit(elem, features, defs, &it).unwrap_or(it);
99 code.append_all(val);
100 code.append_all(quote!(,));
101 }
102 Some(quote! {
103 (#code)
104 })
105 }
106
107 fn token_punct_visit(repr: &str, name: &TokenStream) -> TokenStream {
108 let ty: TokenStream = syn::parse_str(&format!("Token![{}]", repr)).unwrap();
109 quote! {
110 #ty(tokens_helper(_visitor, &#name.spans))
111 }
112 }
113
114 fn token_keyword_visit(repr: &str, name: &TokenStream) -> TokenStream {
115 let ty: TokenStream = syn::parse_str(&format!("Token![{}]", repr)).unwrap();
116 quote! {
117 #ty(tokens_helper(_visitor, &#name.span))
118 }
119 }
120
121 fn token_group_visit(ty: &str, name: &TokenStream) -> TokenStream {
122 let ty = Ident::new(ty, Span::call_site());
123 quote! {
124 #ty(tokens_helper(_visitor, &#name.span))
125 }
126 }
127
128 fn visit(
129 ty: &types::Type,
130 features: &types::Features,
131 defs: &types::Definitions,
132 name: &TokenStream,
133 ) -> Option<TokenStream> {
134 match ty {
135 types::Type::Box(t) => box_visit(&*t, features, defs, name),
136 types::Type::Vec(t) => vec_visit(&*t, features, defs, name),
137 types::Type::Punctuated(p) => punctuated_visit(&p.element, features, defs, name),
138 types::Type::Option(t) => option_visit(&*t, features, defs, name),
139 types::Type::Tuple(t) => tuple_visit(t, features, defs, name),
140 types::Type::Token(t) => {
141 let repr = &defs.tokens[t];
142 let is_keyword = repr.chars().next().unwrap().is_alphabetic();
143 if is_keyword {
144 Some(token_keyword_visit(repr, name))
145 } else {
146 Some(token_punct_visit(repr, name))
147 }
148 }
149 types::Type::Group(t) => Some(token_group_visit(&t[..], name)),
150 types::Type::Syn(t) => {
151 fn requires_full(features: &types::Features) -> bool {
152 features.any.contains("full") && features.any.len() == 1
153 }
154
155 let res = simple_visit(t, name);
156
157 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
158
159 Some(
160 if requires_full(&target.features) && !requires_full(features) {
161 quote! {
162 full!(#res)
163 }
164 } else {
165 res
166 },
167 )
168 }
169 types::Type::Ext(t) if super::TERMINAL_TYPES.contains(&&t[..]) => {
170 Some(simple_visit(t, name))
171 }
172 types::Type::Ext(_) | types::Type::Std(_) => None,
173 }
174 }
175
176 fn visit_features(features: &types::Features) -> TokenStream {
177 let features = &features.any;
178 match features.len() {
179 0 => quote!(),
180 1 => quote!(#[cfg(feature = #(#features)*)]),
181 _ => quote!(#[cfg(any(#(feature = #features),*))]),
182 }
183 }
184
185 pub fn generate(state: &mut State, s: &types::Node, defs: &types::Definitions) {
186 let features = visit_features(&s.features);
187 let under_name = under_name(&s.ident);
188 let ty = Ident::new(&s.ident, Span::call_site());
189 let fold_fn = Ident::new(&format!("fold_{}", under_name), Span::call_site());
190
191 let mut fold_impl = TokenStream::new();
192
193 match &s.data {
194 types::Data::Enum(variants) => {
195 let mut fold_variants = TokenStream::new();
196
197 for (variant, fields) in variants {
198 let variant_ident = Ident::new(variant, Span::call_site());
199
200 if fields.is_empty() {
201 fold_variants.append_all(quote! {
202 #ty::#variant_ident => {
203 #ty::#variant_ident
204 }
205 });
206 } else {
207 let mut bind_fold_fields = TokenStream::new();
208 let mut fold_fields = TokenStream::new();
209
210 for (idx, ty) in fields.iter().enumerate() {
211 let name = format!("_binding_{}", idx);
212 let binding = Ident::new(&name, Span::call_site());
213
214 bind_fold_fields.append_all(quote! {
215 #binding,
216 });
217
218 let owned_binding = quote!(#binding);
219
220 fold_fields.append_all(
221 visit(ty, &s.features, defs, &owned_binding)
222 .unwrap_or(owned_binding),
223 );
224
225 fold_fields.append_all(quote!(,));
226 }
227
228 fold_variants.append_all(quote! {
229 #ty::#variant_ident(#bind_fold_fields) => {
230 #ty::#variant_ident(
231 #fold_fields
232 )
233 }
234 });
235 }
236 }
237
238 fold_impl.append_all(quote! {
239 match _i {
240 #fold_variants
241 }
242 });
243 }
244 types::Data::Struct(fields) => {
245 let mut fold_fields = TokenStream::new();
246
247 for (field, ty) in fields {
248 let id = Ident::new(&field, Span::call_site());
249 let ref_toks = quote!(_i.#id);
250 let fold = visit(&ty, &s.features, defs, &ref_toks).unwrap_or(ref_toks);
251
252 fold_fields.append_all(quote! {
253 #id: #fold,
254 });
255 }
256
257 if !fields.is_empty() {
258 fold_impl.append_all(quote! {
259 #ty {
260 #fold_fields
261 }
262 })
263 } else {
264 if ty == "Ident" {
265 fold_impl.append_all(quote! {
266 let mut _i = _i;
267 let span = _visitor.fold_span(_i.span());
268 _i.set_span(span);
269 });
270 }
271 fold_impl.append_all(quote! {
272 _i
273 });
274 }
275 }
276 types::Data::Private => {
277 if ty == "Ident" {
278 fold_impl.append_all(quote! {
279 let mut _i = _i;
280 let span = _visitor.fold_span(_i.span());
281 _i.set_span(span);
282 });
283 }
284 fold_impl.append_all(quote! {
285 _i
286 });
287 }
288 }
289
290 let include_fold_impl = match &s.data {
291 types::Data::Private => super::TERMINAL_TYPES.contains(&s.ident.as_str()),
292 types::Data::Struct(_) | types::Data::Enum(_) => true,
293 };
294
295 state.fold_trait.append_all(quote! {
296 #features
297 fn #fold_fn(&mut self, i: #ty) -> #ty {
298 #fold_fn(self, i)
299 }
300 });
301
302 if include_fold_impl {
303 state.fold_impl.append_all(quote! {
304 #features
305 pub fn #fold_fn<V: Fold + ?Sized>(
306 _visitor: &mut V, _i: #ty
307 ) -> #ty {
308 #fold_impl
309 }
310 });
311 }
312 }
313}
314
315const TERMINAL_TYPES: &[&str] = &["Span", "Ident"];
316
317pub fn generate(defs: &types::Definitions) {
318 let mut state = codegen::State::default();
319 for s in &defs.types {
320 codegen::generate(&mut state, s, defs);
321 }
322 for tt in TERMINAL_TYPES {
323 let s = types::Node {
324 ident: tt.to_string(),
325 features: types::Features::default(),
326 data: types::Data::Private,
327 };
328 codegen::generate(&mut state, &s, defs);
329 }
330
331 let full_macro = quote! {
332 #[cfg(feature = "full")]
333 macro_rules! full {
334 ($e:expr) => {
335 $e
336 };
337 }
338
339 #[cfg(all(feature = "derive", not(feature = "full")))]
340 macro_rules! full {
341 ($e:expr) => {
342 unreachable!()
343 };
344 }
345 };
346
347 let fold_trait = state.fold_trait;
348 let fold_impl = state.fold_impl;
349 file::write(
350 FOLD_SRC,
351 quote! {
352 // Unreachable code is generated sometimes without the full feature.
353 #![allow(unreachable_code)]
354
355 use *;
356 #[cfg(any(feature = "full", feature = "derive"))]
357 use token::{Brace, Bracket, Paren, Group};
358 use proc_macro2::Span;
359 #[cfg(any(feature = "full", feature = "derive"))]
360 use gen::helper::fold::*;
361
362 #full_macro
363
364 /// Syntax tree traversal to transform the nodes of an owned syntax tree.
365 ///
366 /// See the [module documentation] for details.
367 ///
368 /// [module documentation]: index.html
369 ///
370 /// *This trait is available if Syn is built with the `"fold"` feature.*
371 pub trait Fold {
372 #fold_trait
373 }
374
375 #[cfg(any(feature = "full", feature = "derive"))]
376 macro_rules! fold_span_only {
377 ($f:ident : $t:ident) => {
378 pub fn $f<V: Fold + ?Sized>(_visitor: &mut V, mut _i: $t) -> $t {
379 let span = _visitor.fold_span(_i.span());
380 _i.set_span(span);
381 _i
382 }
383 }
384 }
385
386 #[cfg(any(feature = "full", feature = "derive"))]
387 fold_span_only!(fold_lit_byte: LitByte);
388 #[cfg(any(feature = "full", feature = "derive"))]
389 fold_span_only!(fold_lit_byte_str: LitByteStr);
390 #[cfg(any(feature = "full", feature = "derive"))]
391 fold_span_only!(fold_lit_char: LitChar);
392 #[cfg(any(feature = "full", feature = "derive"))]
393 fold_span_only!(fold_lit_float: LitFloat);
394 #[cfg(any(feature = "full", feature = "derive"))]
395 fold_span_only!(fold_lit_int: LitInt);
396 #[cfg(any(feature = "full", feature = "derive"))]
397 fold_span_only!(fold_lit_str: LitStr);
398
399 #fold_impl
400 },
401 );
402}