semantics_derive/
lib.rs

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}