diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | util-macros/Cargo.toml | 2 | ||||
-rw-r--r-- | util-macros/src/lib.rs | 60 |
3 files changed, 63 insertions, 0 deletions
@@ -666,6 +666,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", + "syn", ] [[package]] diff --git a/util-macros/Cargo.toml b/util-macros/Cargo.toml index b8a14e5..efd017d 100644 --- a/util-macros/Cargo.toml +++ b/util-macros/Cargo.toml @@ -9,3 +9,5 @@ proc-macro = true [dependencies] quote = "1.0.35" proc-macro2 = "1.0.78" +syn = { version = "2.0.51", features = ["full"] } + diff --git a/util-macros/src/lib.rs b/util-macros/src/lib.rs index 26f1936..83b5f58 100644 --- a/util-macros/src/lib.rs +++ b/util-macros/src/lib.rs @@ -1,5 +1,6 @@ use proc_macro::{TokenStream, TokenTree}; use quote::quote; +use syn::{parse, Ident, ItemEnum}; /// Subtracts two numbers and calls a given callback macro with the result. Optionally, a /// additional argument (delimeted) can be given which will also be passed to the @@ -139,3 +140,62 @@ pub fn sub(input: TokenStream) -> TokenStream } .into() } + +#[proc_macro_derive(FromRepr)] +pub fn from_repr(input: TokenStream) -> TokenStream +{ + let enum_item = parse::<ItemEnum>(input).unwrap(); + + let repr_attr = enum_item + .attrs + .iter() + .find(|attr| { + attr.path() + .get_ident() + .is_some_and(|attr_ident| attr_ident == "repr") + }) + .unwrap(); + + let repr = repr_attr.parse_args::<Ident>().unwrap(); + + let repr_str = repr.to_string(); + + if !((repr_str.starts_with('u') || repr_str.starts_with('i')) + && repr_str + .chars() + .skip(1) + .all(|character| character.is_ascii_digit())) + { + panic!("Invalid repr. Must be u* or i* where * is a number"); + } + + let variants = enum_item.variants.iter().map(|variant| { + let Some((_, discriminant)) = &variant.discriminant else { + panic!("All variants must have discriminants"); + }; + + (variant.ident.clone(), discriminant.clone()) + }); + + let match_arms = variants.map(|(variant, discriminant)| { + quote! { + #discriminant => Some(Self::#variant), + } + }); + + let enum_ident = enum_item.ident.clone(); + + quote! { + impl #enum_ident + { + pub fn from_repr(repr: #repr) -> Option<Self> + { + match repr { + #(#match_arms)* + _ => None + } + } + } + } + .into() +} |