table_enum_core/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod tests;
4
5use proc_macro2::TokenStream;
6use syn::parse::{Parse, ParseStream};
7use syn::punctuated::Punctuated;
8use syn::token::Comma;
9use syn::{parse2, parse_quote, Expr, ExprMatch, Field, Fields, Ident, ImplItemFn, Token, Variant, Visibility, Attribute};
10
11#[derive(PartialEq, Eq)]
12enum FieldKind {
13    Normal,
14    Option,
15    Default,
16    Constructor,
17}
18
19#[derive(Debug)]
20struct TableEnum {
21    attrs: Vec<Attribute>,
22    visibility: Visibility,
23    ident: Ident,
24    types: Punctuated<Field, Comma>,
25    members: Punctuated<TableEnumVariant, Comma>,
26}
27
28#[derive(Debug)]
29struct TableEnumVariant {
30    ident: Ident,
31    values: Punctuated<Expr, Comma>,
32}
33
34impl Parse for TableEnumVariant {
35    fn parse(input: ParseStream) -> syn::Result<Self> {
36        let name: Ident = input.parse()?;
37        let content;
38        syn::parenthesized!(content in input);
39        let values = content.parse_terminated(Expr::parse, Token![,])?;
40        return Ok(TableEnumVariant {
41            ident: name,
42            values,
43        });
44    }
45}
46
47impl Parse for TableEnum {
48    fn parse(input: ParseStream) -> syn::Result<Self> {
49        let attrs = input.call(Attribute::parse_outer)?;
50        let lookahead = input.lookahead1();
51        let visibility: Visibility = if lookahead.peek(Token![pub]) {
52            input.parse()?
53        } else {
54            Visibility::Inherited
55        };
56        input.parse::<Token![enum]>()?;
57        let name: Ident = input.parse()?;
58        let content;
59        syn::parenthesized!(content in input);
60        let types = content.parse_terminated(syn::Field::parse_named, Token![,])?;
61        let content;
62        syn::braced!(content in input);
63        let members = content.parse_terminated(TableEnumVariant::parse, Token![,])?;
64        Ok(TableEnum {
65            attrs,
66            visibility,
67            ident: name,
68            types,
69            members,
70        })
71    }
72}
73
74pub fn table_enum_core(input: TokenStream) -> TokenStream {
75    let table_enum = match parse2::<TableEnum>(input) {
76        Ok(ast) => ast,
77        Err(err) => return err.to_compile_error(),
78    };
79    let enum_attrs = table_enum.attrs;
80    let enum_visibility = table_enum.visibility;
81    let enum_name = table_enum.ident;
82    let mut enum_variants = Punctuated::<Variant, Comma>::new();
83    for m in &table_enum.members {
84        enum_variants.push(Variant {
85            attrs: Vec::new(),
86            ident: m.ident.clone(),
87            fields: Fields::Unit,
88            discriminant: None,
89        });
90    }
91    if !enum_variants.trailing_punct() {
92        enum_variants.push_punct(parse_quote!(,))
93    }
94    let variant_names = enum_variants
95        .iter()
96        .map(|v| v.ident.clone())
97        .collect::<Vec<Ident>>();
98    let mut getters = Vec::<ImplItemFn>::new();
99    for i in 0..table_enum.types.len() {
100        let f: &Field = &table_enum.types[i];
101        let f_type = f.ty.clone();
102        let mut field_kind = FieldKind::Normal;
103        for a in &f.attrs {
104            if let Some(segment) = a.path().segments.first() {
105                let attribute_name = segment.ident.to_string();
106                if attribute_name == "option" {
107                    field_kind = FieldKind::Option;
108                    break;
109                }
110                else if attribute_name == "default" {
111                    field_kind = FieldKind::Default;
112                    break;
113                }
114                else if attribute_name == "constructor" {
115                    field_kind = FieldKind::Constructor;
116                    break;
117                }
118            }
119            return parse_quote!( compile_error!("unknown attribute, only #[option], #[default], and #[constructor] are supported") );
120        }
121        let getter_name = f.ident.clone().unwrap();
122        let getter_type = if field_kind == FieldKind::Option { parse_quote!( Option<#f_type> )} else { f_type.clone() };
123        let variant_values: Vec<_> = table_enum.members.iter().map(|v| {
124            let value = v.values[i].clone();
125            if value == parse_quote!( _ ) {
126                match field_kind {
127                    FieldKind::Option => parse_quote!( None ),
128                    FieldKind::Default => parse_quote!( #f_type::default() ),
129                    _ => return parse_quote!( compile_error!("Usage of `_` is valid for #[option] and #[default] fields") ),
130                }
131            }
132            else if field_kind == FieldKind::Option {
133                parse_quote!( Some(#value) )
134            }
135            else {
136                value
137            }
138        }).collect();
139        let match_block: ExprMatch = parse_quote!(
140            match self {
141                #(#enum_name::#variant_names => #variant_values,)*
142            }
143        );
144        // Default::default() is not const so we cannot make a "const fn"
145        let const_fn: TokenStream = if field_kind == FieldKind::Default { parse_quote!( fn ) } else {parse_quote!( const fn) };
146        let getter: ImplItemFn = parse_quote!(
147            #enum_visibility #const_fn #getter_name(&self) -> #getter_type {
148                #match_block
149            }
150        );
151        getters.push(getter);
152        if field_kind == FieldKind::Constructor {
153            let match_block: ExprMatch = parse_quote!(
154                match #getter_name {
155                    #(#variant_values => Some(#enum_name::#variant_names),)*
156                    _ => None
157                }
158            );
159            let constructor = parse_quote!(
160                #enum_visibility fn new(#getter_name: #f_type) -> Option<Self> {
161                    #match_block
162                }
163            );
164            getters.push(constructor)
165        }
166    }
167    parse_quote!(
168        #(#enum_attrs)*
169        #enum_visibility enum #enum_name {
170            #enum_variants
171        }
172        impl #enum_name {
173            #(#getters)*
174        }
175    )
176}