blob: cd43d6055d6d43abae114213f948137689d08bad [file] [log] [blame]
Carl Lerche058ff472019-02-13 16:23:52 -08001use crate::types;
2
3use proc_macro2::Span;
4use syn::{Data, DataStruct, DeriveInput, Ident, Item};
5
6use std::collections::BTreeMap;
7use std::fs::File;
8use std::io::Read;
9use std::path::Path;
10
11const SYN_CRATE_ROOT: &str = "../src/lib.rs";
12const TOKEN_SRC: &str = "../src/token.rs";
13const IGNORED_MODS: &[&str] = &["fold", "visit", "visit_mut"];
14const EXTRA_TYPES: &[&str] = &["Lifetime"];
15const TERMINAL_TYPES: &[&str] = &["Span", "Ident"];
16
17// NOTE: BTreeMap is used here instead of HashMap to have deterministic output.
18type ItemLookup = BTreeMap<Ident, AstItem>;
19type TokenLookup = BTreeMap<String, String>;
20
21/// Parse the contents of `src` and return a list of AST types.
David Tolnayf9bb8ff2019-02-15 13:10:14 -080022pub fn parse() -> types::Definitions {
Carl Lerche058ff472019-02-13 16:23:52 -080023 let mut item_lookup = BTreeMap::new();
24 load_file(SYN_CRATE_ROOT, &[], &mut item_lookup).unwrap();
25
26 let token_lookup = load_token_file(TOKEN_SRC).unwrap();
27
28 // Load in any terminal types
29 for &tt in TERMINAL_TYPES {
30 use syn::*;
31 item_lookup.insert(
32 Ident::new(&tt, Span::call_site()),
33 AstItem {
34 ast: DeriveInput {
35 ident: Ident::new(tt, Span::call_site()),
36 vis: Visibility::Public(VisPublic {
37 pub_token: <Token![pub]>::default(),
38 }),
39 attrs: vec![],
40 generics: Generics::default(),
41 data: Data::Struct(DataStruct {
42 fields: Fields::Unit,
43 struct_token: <Token![struct]>::default(),
44 semi_token: None,
45 }),
46 },
47 features: vec![],
48 },
49 );
50 }
51
David Tolnayf9bb8ff2019-02-15 13:10:14 -080052 let types = item_lookup
Carl Lerche058ff472019-02-13 16:23:52 -080053 .values()
54 .map(|item| introspect_item(item, &item_lookup, &token_lookup))
David Tolnayf9bb8ff2019-02-15 13:10:14 -080055 .collect();
56
57 let tokens = token_lookup.into_iter().map(|(name, ty)| (ty, name)).collect();
58
59 types::Definitions { types, tokens }
Carl Lerche058ff472019-02-13 16:23:52 -080060}
61
62/// Data extracted from syn source
63#[derive(Clone)]
64pub struct AstItem {
65 ast: DeriveInput,
66 features: Vec<syn::Attribute>,
67}
68
David Tolnayf9bb8ff2019-02-15 13:10:14 -080069fn introspect_item(item: &AstItem, items: &ItemLookup, tokens: &TokenLookup) -> types::Node {
Carl Lerche058ff472019-02-13 16:23:52 -080070 let features = introspect_features(&item.features);
71
72 match &item.ast.data {
David Tolnayf9bb8ff2019-02-15 13:10:14 -080073 Data::Enum(ref data) => types::Node::Enum(introspect_enum(
Carl Lerche058ff472019-02-13 16:23:52 -080074 &item.ast.ident,
75 features,
76 data,
77 items,
78 tokens,
79 )),
David Tolnayf9bb8ff2019-02-15 13:10:14 -080080 Data::Struct(ref data) => types::Node::Struct(introspect_struct(
Carl Lerche058ff472019-02-13 16:23:52 -080081 &item.ast.ident,
82 features,
83 data,
84 items,
85 tokens,
86 )),
87 Data::Union(..) => panic!("Union not supported"),
88 }
89}
90
91fn introspect_enum(
92 ident: &Ident,
93 features: types::Features,
94 item: &syn::DataEnum,
95 items: &ItemLookup,
96 tokens: &TokenLookup,
97) -> types::Enum {
98 let variants = item
99 .variants
100 .iter()
101 .map(|variant| {
102 let fields = match &variant.fields {
103 syn::Fields::Unnamed(fields) => fields
104 .unnamed
105 .iter()
106 .map(|field| introspect_type(&field.ty, items, tokens))
107 .collect(),
108 syn::Fields::Unit => vec![],
109 _ => panic!("Enum representation not supported"),
110 };
111
112 types::Variant::new(variant.ident.to_string(), fields)
113 })
114 .collect();
115
116 types::Enum::new(ident.to_string(), features, variants)
117}
118
119fn introspect_struct(
120 ident: &Ident,
121 features: types::Features,
122 item: &syn::DataStruct,
123 items: &ItemLookup,
124 tokens: &TokenLookup,
125) -> types::Struct {
126 let mut all_fields_pub = true;
127 let fields = match &item.fields {
128 syn::Fields::Named(fields) => fields
129 .named
130 .iter()
131 .map(|field| {
132 if !is_pub(&field.vis) {
133 all_fields_pub = false;
134 }
135
136 types::Field::new(
137 field.ident.as_ref().unwrap().to_string(),
138 introspect_type(&field.ty, items, tokens),
139 )
140 })
141 .collect(),
142 syn::Fields::Unit => vec![],
143 _ => panic!("Struct representation not supported"),
144 };
145
146 types::Struct::new(ident.to_string(), features, fields, all_fields_pub)
147}
148
149fn introspect_type(item: &syn::Type, items: &ItemLookup, tokens: &TokenLookup) -> types::Type {
150 match item {
151 syn::Type::Path(syn::TypePath {
152 qself: None,
153 ref path,
154 }) => {
155 let last = path.segments.last().unwrap().into_value();
156
157 match &last.ident.to_string()[..] {
158 "Option" => {
159 let nested = introspect_type(first_arg(&last.arguments), items, tokens);
160 types::Type::Option(Box::new(nested))
161 }
162 "Punctuated" => {
163 let nested = introspect_type(first_arg(&last.arguments), items, tokens);
164 let punct = match introspect_type(last_arg(&last.arguments), items, tokens) {
165 types::Type::Token(s) => s,
166 _ => panic!(),
167 };
168
David Tolnay295141b2019-02-15 12:45:33 -0800169 types::Type::Punctuated(types::Punctuated::new(nested, punct))
Carl Lerche058ff472019-02-13 16:23:52 -0800170 }
171 "Vec" => {
172 let nested = introspect_type(first_arg(&last.arguments), items, tokens);
173 types::Type::Vec(Box::new(nested))
174 }
175 "Box" => {
176 let nested = introspect_type(first_arg(&last.arguments), items, tokens);
177 types::Type::Box(Box::new(nested))
178 }
179 "Brace" | "Bracket" | "Paren" | "Group" => {
David Tolnay295141b2019-02-15 12:45:33 -0800180 types::Type::Group(last.ident.to_string())
Carl Lerche058ff472019-02-13 16:23:52 -0800181 }
182 "TokenStream" | "Literal" => types::Type::Ext(last.ident.to_string()),
183 "String" | "u32" | "usize" | "bool" => types::Type::Std(last.ident.to_string()),
184 _ => {
185 if items.get(&last.ident).is_some() {
186 types::Type::Item(last.ident.to_string())
187 } else {
188 unimplemented!("{}", last.ident.to_string());
189 }
190 }
191 }
192 }
193 syn::Type::Tuple(syn::TypeTuple { ref elems, .. }) => {
194 let tys = elems
195 .iter()
196 .map(|ty| introspect_type(&ty, items, tokens))
197 .collect();
198 types::Type::Tuple(tys)
199 }
200 syn::Type::Macro(syn::TypeMacro { ref mac })
201 if mac.path.segments.last().unwrap().into_value().ident == "Token" =>
202 {
203 let content = mac.tts.to_string();
204 let ty = tokens.get(&content).unwrap().to_string();
205
206 types::Type::Token(types::Token::new(content, ty))
207 }
208 _ => panic!("{}", quote!(#item).to_string()),
209 }
210}
211
212fn introspect_features(attrs: &[syn::Attribute]) -> types::Features {
213 let mut ret = types::Features::default();
214
215 for attr in attrs {
216 if !attr.path.is_ident("cfg") {
217 continue;
218 }
219
220 let features: types::Features = syn::parse2(attr.tts.clone()).unwrap();
221 ret.join(&features);
222 }
223
224 ret
225}
226
227fn is_pub(vis: &syn::Visibility) -> bool {
228 match vis {
229 syn::Visibility::Public(_) => true,
230 _ => false,
231 }
232}
233
234fn first_arg(params: &syn::PathArguments) -> &syn::Type {
235 let data = match *params {
236 syn::PathArguments::AngleBracketed(ref data) => data,
237 _ => panic!("Expected at least 1 type argument here"),
238 };
239
240 match **data
241 .args
242 .first()
243 .expect("Expected at least 1 type argument here")
244 .value()
245 {
246 syn::GenericArgument::Type(ref ty) => ty,
247 _ => panic!("Expected at least 1 type argument here"),
248 }
249}
250
251fn last_arg(params: &syn::PathArguments) -> &syn::Type {
252 let data = match *params {
253 syn::PathArguments::AngleBracketed(ref data) => data,
254 _ => panic!("Expected at least 1 type argument here"),
255 };
256
257 match **data
258 .args
259 .last()
260 .expect("Expected at least 1 type argument here")
261 .value()
262 {
263 syn::GenericArgument::Type(ref ty) => ty,
264 _ => panic!("Expected at least 1 type argument here"),
265 }
266}
267
268mod parsing {
269 use super::{AstItem, TokenLookup};
270 use crate::types;
271
272 use proc_macro2::TokenStream;
273 use syn;
274 use syn::parse::{Parse, ParseStream, Result};
275 use syn::*;
276
277 use std::collections::BTreeMap;
278
279 fn peek_tag(input: ParseStream, tag: &str) -> bool {
280 let ahead = input.fork();
281 ahead.parse::<Token![#]>().is_ok()
282 && ahead
283 .parse::<Ident>()
284 .map(|ident| ident == tag)
285 .unwrap_or(false)
286 }
287
288 // Parses #full - returns #[cfg(feature = "full")] if it is present, and
289 // nothing otherwise.
290 fn full(input: ParseStream) -> Vec<syn::Attribute> {
291 if peek_tag(input, "full") {
292 input.parse::<Token![#]>().unwrap();
293 input.parse::<Ident>().unwrap();
294 vec![parse_quote!(#[cfg(feature = "full")])]
295 } else {
296 vec![]
297 }
298 }
299
300 fn skip_manual_extra_traits(input: ParseStream) {
301 if peek_tag(input, "manual_extra_traits") {
302 input.parse::<Token![#]>().unwrap();
303 input.parse::<Ident>().unwrap();
304 }
305 }
306
307 // Parses a simple AstStruct without the `pub struct` prefix.
308 fn ast_struct_inner(input: ParseStream) -> Result<AstItem> {
309 let ident: Ident = input.parse()?;
310 let features = full(input);
311 skip_manual_extra_traits(input);
312 let rest: TokenStream = input.parse()?;
313 Ok(AstItem {
314 ast: syn::parse2(quote! {
315 pub struct #ident #rest
316 })?,
317 features,
318 })
319 }
320
321 // ast_struct! parsing
322 pub struct AstStruct(pub(super) Vec<AstItem>);
323 impl Parse for AstStruct {
324 fn parse(input: ParseStream) -> Result<Self> {
325 input.call(Attribute::parse_outer)?;
326 input.parse::<Token![pub]>()?;
327 input.parse::<Token![struct]>()?;
328 let res = input.call(ast_struct_inner)?;
329 Ok(AstStruct(vec![res]))
330 }
331 }
332
333 fn no_visit(input: ParseStream) -> bool {
334 if peek_tag(input, "no_visit") {
335 input.parse::<Token![#]>().unwrap();
336 input.parse::<Ident>().unwrap();
337 true
338 } else {
339 false
340 }
341 }
342
343 // ast_enum! parsing
344 pub struct AstEnum(pub Vec<AstItem>);
345 impl Parse for AstEnum {
346 fn parse(input: ParseStream) -> Result<Self> {
347 input.call(Attribute::parse_outer)?;
348 input.parse::<Token![pub]>()?;
349 input.parse::<Token![enum]>()?;
350 let ident: Ident = input.parse()?;
351 let no_visit = no_visit(input);
352 let rest: TokenStream = input.parse()?;
353 Ok(AstEnum(if no_visit {
354 vec![]
355 } else {
356 vec![AstItem {
357 ast: syn::parse2(quote! {
358 pub enum #ident #rest
359 })?,
360 features: vec![],
361 }]
362 }))
363 }
364 }
365
366 // A single variant of an ast_enum_of_structs!
367 struct EosVariant {
368 name: Ident,
369 member: Option<Path>,
370 inner: Option<AstItem>,
371 }
372 fn eos_variant(input: ParseStream) -> Result<EosVariant> {
373 input.call(Attribute::parse_outer)?;
374 input.parse::<Token![pub]>()?;
375 let variant: Ident = input.parse()?;
376 let (member, inner) = if input.peek(token::Paren) {
377 let content;
378 parenthesized!(content in input);
379 if content.fork().call(ast_struct_inner).is_ok() {
380 let item = content.call(ast_struct_inner)?;
381 (Some(Path::from(item.ast.ident.clone())), Some(item))
382 } else {
383 let path: Path = content.parse()?;
384 (Some(path), None)
385 }
386 } else {
387 (None, None)
388 };
389 input.parse::<Token![,]>()?;
390 Ok(EosVariant {
391 name: variant,
392 member,
393 inner,
394 })
395 }
396
397 // ast_enum_of_structs! parsing
398 pub struct AstEnumOfStructs(pub Vec<AstItem>);
399 impl Parse for AstEnumOfStructs {
400 fn parse(input: ParseStream) -> Result<Self> {
401 input.call(Attribute::parse_outer)?;
402 input.parse::<Token![pub]>()?;
403 input.parse::<Token![enum]>()?;
404 let ident: Ident = input.parse()?;
405
406 let content;
407 braced!(content in input);
408 let mut variants = Vec::new();
409 while !content.is_empty() {
410 variants.push(content.call(eos_variant)?);
411 }
412
413 if let Some(ident) = input.parse::<Option<Ident>>()? {
414 assert_eq!(ident, "do_not_generate_to_tokens");
415 }
416
417 let enum_item = {
418 let variants = variants.iter().map(|v| {
419 let name = v.name.clone();
420 match v.member {
421 Some(ref member) => quote!(#name(#member)),
422 None => quote!(#name),
423 }
424 });
425 parse_quote! {
426 pub enum #ident {
427 #(#variants),*
428 }
429 }
430 };
431 let mut items = vec![AstItem {
432 ast: enum_item,
433 features: vec![],
434 }];
435 items.extend(variants.into_iter().filter_map(|v| v.inner));
436 Ok(AstEnumOfStructs(items))
437 }
438 }
439
440 pub struct TokenMacro(pub TokenLookup);
441 impl Parse for TokenMacro {
442 fn parse(input: ParseStream) -> Result<Self> {
443 let mut tokens = BTreeMap::new();
444 while !input.is_empty() {
445 let content;
446 parenthesized!(content in input);
447 let token = content.parse::<TokenStream>()?.to_string();
448 input.parse::<Token![=]>()?;
449 input.parse::<Token![>]>()?;
450 let content;
451 braced!(content in input);
452 input.parse::<Token![;]>()?;
453 content.parse::<token::Dollar>()?;
454 let path: Path = content.parse()?;
455 let ty = path.segments.last().unwrap().into_value().ident.to_string();
456 tokens.insert(token, ty.to_string());
457 }
458 Ok(TokenMacro(tokens))
459 }
460 }
461
462 fn parse_feature(input: ParseStream) -> Result<String> {
463 let i: syn::Ident = input.parse()?;
464 assert_eq!(i, "feature");
465
466 input.parse::<Token![=]>()?;
467 let s = input.parse::<syn::LitStr>()?;
468
469 Ok(s.value())
470 }
471
472 impl Parse for types::Features {
473 fn parse(input: ParseStream) -> Result<Self> {
474 let mut features = vec![];
475
476 let level_1;
477 parenthesized!(level_1 in input);
478
479 let i: syn::Ident = level_1.fork().parse()?;
480
481 if i == "any" {
482 level_1.parse::<syn::Ident>()?;
483
484 let level_2;
485 parenthesized!(level_2 in level_1);
486
487 while !level_2.is_empty() {
488 features.push(parse_feature(&level_2)?);
489
490 if !level_2.is_empty() {
491 level_2.parse::<Token![,]>()?;
492 }
493 }
494 } else if i == "feature" {
495 features.push(parse_feature(&level_1)?);
496 assert!(level_1.is_empty());
497 } else {
498 panic!("{:?}", i);
499 }
500
501 assert!(input.is_empty());
502
503 Ok(types::Features::new(features))
504 }
505 }
506}
507
508fn get_features(attrs: &[syn::Attribute], base: &[syn::Attribute]) -> Vec<syn::Attribute> {
509 let mut ret = base.to_owned();
510
511 for attr in attrs {
512 if attr.path.is_ident("cfg") {
513 ret.push(attr.clone());
514 }
515 }
516
517 ret
518}
519
520type Error = Box<::std::error::Error>;
521
522fn load_file<P: AsRef<Path>>(
523 name: P,
524 features: &[syn::Attribute],
525 lookup: &mut ItemLookup,
526) -> Result<(), Error> {
527 let name = name.as_ref();
528 let parent = name.parent().ok_or("no parent path")?;
529
530 let mut f = File::open(name)?;
531 let mut src = String::new();
532 f.read_to_string(&mut src)?;
533
534 // Parse the file
535 let file = syn::parse_file(&src)?;
536
537 // Collect all of the interesting AstItems declared in this file or submodules.
538 'items: for item in file.items {
539 match item {
540 Item::Mod(item) => {
541 // Don't inspect inline modules.
542 if item.content.is_some() {
543 continue;
544 }
545
546 // We don't want to try to load the generated rust files and
547 // parse them, so we ignore them here.
548 for name in IGNORED_MODS {
549 if item.ident == name {
550 continue 'items;
551 }
552 }
553
554 // Lookup any #[cfg()] attributes on the module and add them to
555 // the feature set.
556 //
557 // The derive module is weird because it is built with either
558 // `full` or `derive` but exported only under `derive`.
559 let features = if item.ident == "derive" {
560 vec![parse_quote!(#[cfg(feature = "derive")])]
561 } else {
562 get_features(&item.attrs, features)
563 };
564
565 // Look up the submodule file, and recursively parse it.
566 // XXX: Only handles same-directory .rs file submodules.
567 let path = parent.join(&format!("{}.rs", item.ident));
568 load_file(path, &features, lookup)?;
569 }
570 Item::Macro(item) => {
571 // Lookip any #[cfg()] attributes directly on the macro
572 // invocation, and add them to the feature set.
573 let features = get_features(&item.attrs, features);
574
575 // Try to parse the AstItem declaration out of the item.
576 let tts = &item.mac.tts;
577 let found = if item.mac.path.is_ident("ast_struct") {
578 syn::parse2::<parsing::AstStruct>(quote!(#tts))?.0
579 } else if item.mac.path.is_ident("ast_enum") {
580 syn::parse2::<parsing::AstEnum>(quote!(#tts))?.0
581 } else if item.mac.path.is_ident("ast_enum_of_structs") {
582 syn::parse2::<parsing::AstEnumOfStructs>(quote!(#tts))?.0
583 } else {
584 continue;
585 };
586
587 // Record our features on the parsed AstItems.
588 for mut item in found {
589 item.features.extend(features.clone());
590 lookup.insert(item.ast.ident.clone(), item);
591 }
592 }
593 Item::Struct(item) => {
594 let ident = item.ident;
595 if EXTRA_TYPES.contains(&&ident.to_string()[..]) {
596 lookup.insert(
597 ident.clone(),
598 AstItem {
599 ast: DeriveInput {
600 ident,
601 vis: item.vis,
602 attrs: item.attrs,
603 generics: item.generics,
604 data: Data::Struct(DataStruct {
605 fields: item.fields,
606 struct_token: item.struct_token,
607 semi_token: item.semi_token,
608 }),
609 },
610 features: features.to_owned(),
611 },
612 );
613 }
614 }
615 _ => {}
616 }
617 }
618 Ok(())
619}
620
621fn load_token_file<P: AsRef<Path>>(name: P) -> Result<TokenLookup, Error> {
622 let name = name.as_ref();
623 let mut f = File::open(name)?;
624 let mut src = String::new();
625 f.read_to_string(&mut src)?;
626 let file = syn::parse_file(&src)?;
627 for item in file.items {
628 match item {
629 Item::Macro(item) => {
630 match item.ident {
631 Some(ref i) if i == "Token" => {}
632 _ => continue,
633 }
634 let tts = &item.mac.tts;
635 let tokens = syn::parse2::<parsing::TokenMacro>(quote!(#tts))?.0;
636 return Ok(tokens);
637 }
638 _ => {}
639 }
640 }
641
642 Err("failed to parse Token macro".into())
643}