1use convert_case::{Case, Casing as _};
2use quote::quote;
3use syn::{
4 parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, token::Paren, DataEnum,
5 DeriveInput, Ident, LitInt, LitStr, Token,
6};
7
8struct Annotations {
9 _paren: Paren,
10 annotations: Punctuated<Annotation, Token![,]>,
11}
12
13impl Parse for Annotations {
14 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
15 let in_paren;
16 let _paren = parenthesized!(in_paren in input);
17 let annotations = Punctuated::parse_terminated(&in_paren)?;
18
19 Ok(Self {
20 _paren,
21 annotations,
22 })
23 }
24}
25
26enum Annotation {
27 Name(NameAnnot),
28 Index(IndexAnnot),
29}
30
31impl Parse for Annotation {
32 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
33 let input_ = input.fork();
34
35 if input_.parse::<NameAnnot>().is_ok() {
36 let name_annot = input.parse()?;
37 Ok(Annotation::Name(name_annot))
38 } else {
39 let index_annot = input.parse()?;
40 Ok(Annotation::Index(index_annot))
41 }
42 }
43}
44
45struct NameAnnot {
46 _name: Ident,
47 _equal_token: Token![=],
48 name_value: LitStr,
49}
50
51impl Parse for NameAnnot {
52 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
53 let _name: Ident = input.parse()?;
54
55 if _name.to_string() != "name" {
56 return Err(input.error("path should be name"));
57 }
58
59 let _equal_token = input.parse()?;
60 let name_value = input.parse()?;
61
62 Ok(Self {
63 _name,
64 _equal_token,
65 name_value,
66 })
67 }
68}
69
70struct IndexAnnot {
71 _index: Ident,
72 _equal_token: Token![=],
73 index_value: LitInt,
74}
75
76impl Parse for IndexAnnot {
77 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
78 let _index: Ident = input.parse()?;
79
80 if _index.to_string() != "index" {
81 return Err(input.error("path should be index"));
82 }
83
84 let _equal_token = input.parse()?;
85 let index_value = input.parse()?;
86
87 Ok(Self {
88 _index,
89 _equal_token,
90 index_value,
91 })
92 }
93}
94
95#[proc_macro_derive(Semantics, attributes(sem))]
96pub fn derive_semantics(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
97 let derive_item = parse_macro_input!(item as DeriveInput);
98
99 match derive_item.data {
100 syn::Data::Enum(enum_item) => on_enum(derive_item.ident, enum_item),
101 syn::Data::Struct(_) => todo!(),
102 syn::Data::Union(_) => todo!(),
103 }
104}
105
106fn on_enum(enum_ident: Ident, enum_item: DataEnum) -> proc_macro::TokenStream {
107 let mut explicit_indices = 0;
108 let mut name_branches = Vec::new();
109 let mut index_branches = Vec::new();
110 let varians_len = enum_item.variants.len();
111
112 for (k, variant) in enum_item.variants.into_iter().enumerate() {
113 let k = k as u8;
114 let variant_ident = &variant.ident;
115
116 let mut name = None;
117 let mut index = None;
118
119 for attr in variant.attrs {
120 match attr.path.get_ident() {
121 Some(ident) if ident.to_string() != "sem" => continue,
122 None => continue,
123 _ => (),
124 };
125
126 let tokens = attr.tokens.into();
127 let annots = parse_macro_input!(tokens as Annotations);
128
129 for annot in annots.annotations {
130 match annot {
131 Annotation::Name(name_annot) if name.is_none() => {
132 let name_value = name_annot.name_value;
133 name = Some(quote! { #name_value });
134 }
135
136 Annotation::Index(index_annot) if index.is_none() => {
137 let index_value = index_annot.index_value;
138 index = Some(quote! { #index_value });
139 explicit_indices += 1;
140 }
141
142 _ => {
143 return quote! { compilation_error!("trying to set the same annotation twice") }.into()
144 }
145 }
146 }
147
148 break;
149 }
150
151 if name.is_none() {
152 let new_name = variant.ident.to_string().to_case(Case::Snake);
153 name = Some(quote! { #new_name });
154 }
155
156 if index.is_none() {
157 index = Some(quote! { #k });
158 }
159
160 name_branches.push(quote! {
161 #enum_ident::#variant_ident => #name
162 });
163
164 index_branches.push(quote! {
165 #enum_ident::#variant_ident => #index
166 });
167 }
168
169 if explicit_indices > 0 && explicit_indices < varians_len {
170 return quote! { compilation_error!("some variants have an explicit index and some don’t") }
171 .into();
172 }
173
174 let q = quote! {
175 impl semantics::Semantics for #enum_ident {
176 type Name = &'static str;
177 type Index = u8;
178
179 fn name(&self) -> Self::Name {
180 match *self {
181 #(#name_branches),*
182 }
183 }
184
185 fn index(&self) -> Self::Index {
186 match *self {
187 #(#index_branches),*
188 }
189 }
190 }
191 };
192
193 q.into()
194}