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 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}