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