diff --git a/Cargo.toml b/Cargo.toml index 99f9c8e..1a59156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,13 @@ members = [ "sub/core", "sub/libm", "sub/pe", - "sub/winu" + "sub/winu", + "sub/_macros" ] [workspace.dependencies] -sub_sample = { path = "sub/sample" } sub_core = { path = "sub/core" } sub_libm = { path = "sub/libm" } sub_pe = { path = "sub/pe" } sub_winu = { path = "sub/winu" } +sub_macros = { path = "sub/_macros" } diff --git a/sub/_macros/Cargo.toml b/sub/_macros/Cargo.toml new file mode 100644 index 0000000..196533d --- /dev/null +++ b/sub/_macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sub_macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +proc-macro2 = "1" +syn = { version = "2", features = ["full"] } + diff --git a/sub/_macros/src/from_repr.rs b/sub/_macros/src/from_repr.rs new file mode 100644 index 0000000..5d8fbd0 --- /dev/null +++ b/sub/_macros/src/from_repr.rs @@ -0,0 +1,64 @@ +use proc_macro::TokenStream; + +const VALID: &[&'static str] = &[ + "u8", "u16", "u32", "u64", "u128", + "i8", "i16", "i32", "i64", "i128", +]; + +pub fn derive_from_repr(input: TokenStream) -> TokenStream { + let syn::DeriveInput { attrs, ident, data, .. } = syn::parse_macro_input!(input); + let edata = match data { + syn::Data::Enum(edata) => edata, + _invalid => return TokenStream::from(quote::quote! { compile_error!("The identifier should not be empty"); }), + }; + + let repr_type = attrs.iter() + .filter_map(|attribute| { + // verify it is a repr type + attribute.path().get_ident()? + .to_string().eq("repr") + .then_some(())?; + let mut repr_type: Option = None; + let _ = attribute.parse_nested_meta(|meta| { + for &v in VALID { + if meta.path.is_ident(v) { + repr_type = Some(meta.path); + return Ok(()) + } + } + Err(meta.error("unrecognized repr")) + }); + repr_type + }).next(); + + let repr = match repr_type { + Some(repr) => repr, + None => { + return TokenStream::from(quote::quote! { + compile_error!("PrimitiveEnum requires the #[repr(u/i*)] attribute"); + }) + } + }; + + let values = edata.variants.iter().filter_map(|variant| { + if let Some((_, desc)) = &variant.discriminant { + let variant = &variant.ident; + Some(quote::quote! { + #desc => Some(Self::#variant), + }) + } else { None } + }); + + let name = &ident; + TokenStream::from(quote::quote! { + impl x::FromRepr for #name { + type Repr = #repr; + fn from_repr(repr: #repr) -> Option { + match repr { + #(#values)* + _ => None + } + } + } + }) +} \ No newline at end of file diff --git a/sub/_macros/src/lib.rs b/sub/_macros/src/lib.rs new file mode 100644 index 0000000..e0ad1d4 --- /dev/null +++ b/sub/_macros/src/lib.rs @@ -0,0 +1,14 @@ + +mod from_repr; + + +#[proc_macro_derive(FromRepr)] +pub fn derive_from_repr(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + from_repr::derive_from_repr(input) +} + + + + + + diff --git a/x/Cargo.toml b/x/Cargo.toml index 42a614e..383d3d8 100644 --- a/x/Cargo.toml +++ b/x/Cargo.toml @@ -6,13 +6,16 @@ edition = "2021" [features] default = ["core"] core = ["sub_core"] +macros = ["sub_macros"] libm = ["sub_libm"] pe = ["sub_pe"] + winuser = ["sub_winu", "pe", "sub_pe/windows"] [dependencies] sub_core = { workspace = true, optional = true } sub_libm = { workspace = true, optional = true} sub_pe = { workspace = true, optional = true } -sub_winu = { workspace = true, optional = true } \ No newline at end of file +sub_winu = { workspace = true, optional = true } +sub_macros = { workspace = true, optional = true } \ No newline at end of file diff --git a/x/src/lib.rs b/x/src/lib.rs index 36576ea..935d54c 100644 --- a/x/src/lib.rs +++ b/x/src/lib.rs @@ -14,8 +14,13 @@ macro_rules! import { }; } - import!(sub_core, core, "core"); import!(sub_libm, libm, "libm"); import!(sub_pe, pe, "pe"); import!(sub_winu, win, "winuser"); + +/// the macro crate is a proc macro, so it is a bit different. +#[cfg(feature = "macros")] +pub use sub_macros::{ + FromRepr +}; \ No newline at end of file