aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-13 21:19:03 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-13 21:19:03 +0800
commit17cf59fe8220ac5befbbe8333fa0515ab532103d (patch)
treec2470e6ecc44a74fc40eef8b5bc36901b9e939c6 /mingling_macros
parentb4cef6e601777b8c4b18df118689f8ac310fa835 (diff)
Remove comp module and add EnumTag derive macro
Diffstat (limited to 'mingling_macros')
-rw-r--r--mingling_macros/src/enum_tag.rs164
-rw-r--r--mingling_macros/src/lib.rs12
-rw-r--r--mingling_macros/src/suggest.rs21
3 files changed, 197 insertions, 0 deletions
diff --git a/mingling_macros/src/enum_tag.rs b/mingling_macros/src/enum_tag.rs
new file mode 100644
index 0000000..a30a0d5
--- /dev/null
+++ b/mingling_macros/src/enum_tag.rs
@@ -0,0 +1,164 @@
+//! EnumTag derive macro implementation
+//!
+//! This module provides the `#[derive(EnumTag)]` procedural macro for enums.
+//! The macro generates implementations of the `EnumTag` trait for enums with
+//! unit variants only (no fields). Variants can have an optional `#[enum_desc]`
+//! attribute to provide descriptions.
+
+use proc_macro::TokenStream;
+use proc_macro2::Span;
+use quote::quote;
+use syn::{
+ Attribute, Data, DeriveInput, Error, Expr, Fields, Ident, Lit, LitStr, Meta, MetaNameValue,
+ Result, Variant, parse_macro_input,
+};
+
+pub fn derive_enum_tag(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ match derive_enum_tag_impl(input) {
+ Ok(tokens) => tokens.into(),
+ Err(err) => err.to_compile_error().into(),
+ }
+}
+
+/// Implementation of the EnumTag derive macro
+fn derive_enum_tag_impl(input: DeriveInput) -> Result<proc_macro2::TokenStream> {
+ let enum_name = &input.ident;
+ let generics = &input.generics;
+
+ // Extract enum data
+ let data = match input.data {
+ Data::Enum(data_enum) => data_enum,
+ Data::Struct(_) => {
+ return Err(Error::new_spanned(
+ enum_name,
+ "EnumTag can only be derived for enums, not structs",
+ ));
+ }
+ Data::Union(_) => {
+ return Err(Error::new_spanned(
+ enum_name,
+ "EnumTag can only be derived for enums, not unions",
+ ));
+ }
+ };
+
+ // Process each variant
+ let mut variant_info = Vec::new();
+ let mut match_arms = Vec::new();
+ let mut build_match_arms = Vec::new();
+
+ for variant in data.variants {
+ process_variant(
+ variant,
+ enum_name,
+ &mut variant_info,
+ &mut match_arms,
+ &mut build_match_arms,
+ )?;
+ }
+
+ // Generate the implementation
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let expanded = quote! {
+ impl #impl_generics ::mingling::EnumTag for #enum_name #ty_generics #where_clause {
+ fn enum_info(&self) -> (&'static str, &'static str) {
+ match self {
+ #(#match_arms)*
+ }
+ }
+
+ fn build_enum(name: String) -> Self {
+ match name.as_str() {
+ #(#build_match_arms)*
+ _ => panic!("Invalid enum variant name: {}", name),
+ }
+ }
+
+ fn enums() -> &'static [(&'static str, &'static str)] {
+ &[#(#variant_info),*]
+ }
+ }
+ };
+
+ Ok(expanded)
+}
+
+/// Process a single enum variant
+fn process_variant(
+ variant: Variant,
+ enum_name: &Ident,
+ variant_info: &mut Vec<proc_macro2::TokenStream>,
+ match_arms: &mut Vec<proc_macro2::TokenStream>,
+ build_match_arms: &mut Vec<proc_macro2::TokenStream>,
+) -> Result<()> {
+ let variant_name = variant.ident.clone();
+
+ // Check if variant has fields
+ match &variant.fields {
+ Fields::Unit => {
+ // Good, unit variant
+ }
+ Fields::Named(_) | Fields::Unnamed(_) => {
+ return Err(Error::new_spanned(
+ &variant,
+ format!(
+ "EnumTag cannot be derived for enum variant `{}` with fields. Only unit variants are supported.",
+ variant_name
+ ),
+ ));
+ }
+ }
+
+ // Extract description from #[enum_desc] attribute
+ let description = extract_description(&variant.attrs, &variant_name)?;
+
+ // Generate tokens for this variant
+ let variant_name_str = variant_name.to_string();
+ let description_str = description.value();
+
+ variant_info.push(quote! {
+ (#variant_name_str, #description_str)
+ });
+
+ match_arms.push(quote! {
+ #enum_name::#variant_name => (#variant_name_str, #description_str),
+ });
+
+ build_match_arms.push(quote! {
+ #variant_name_str => #enum_name::#variant_name,
+ });
+
+ Ok(())
+}
+
+/// Extract description from #[enum_desc] attribute
+fn extract_description(attrs: &[Attribute], variant_name: &Ident) -> Result<LitStr> {
+ for attr in attrs {
+ if attr.path().is_ident("enum_desc") {
+ return match attr.parse_args::<Meta>() {
+ Ok(Meta::NameValue(MetaNameValue {
+ value:
+ Expr::Lit(syn::PatLit {
+ lit: Lit::Str(lit_str),
+ ..
+ }),
+ ..
+ })) => Ok(lit_str),
+ Ok(_) => Err(Error::new_spanned(
+ attr,
+ "#[enum_desc] attribute must be in the form `#[enum_desc(\"description\")]`",
+ )),
+ Err(_) => Err(Error::new_spanned(
+ attr,
+ "Failed to parse #[enum_desc] attribute",
+ )),
+ };
+ }
+ }
+
+ // If no #[enum_desc] attribute, use variant name as description
+ Ok(LitStr::new(&variant_name.to_string(), Span::call_site()))
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index f97c458..a989ce3 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -15,6 +15,7 @@ mod chain;
#[cfg(feature = "comp")]
mod completion;
mod dispatcher_chain;
+mod enum_tag;
mod groupped;
mod node;
mod pack;
@@ -98,6 +99,11 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream {
groupped::derive_groupped(input)
}
+#[proc_macro_derive(EnumTag, attributes(enum_desc))]
+pub fn derive_enum_tag(input: TokenStream) -> TokenStream {
+ enum_tag::derive_enum_tag(input)
+}
+
#[cfg(feature = "general_renderer")]
#[proc_macro_derive(GrouppedSerialize, attributes(group))]
pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
@@ -343,6 +349,12 @@ pub fn suggest(input: TokenStream) -> TokenStream {
suggest::suggest(input)
}
+#[cfg(feature = "comp")]
+#[proc_macro]
+pub fn suggest_enum(input: TokenStream) -> TokenStream {
+ suggest::suggest_enum(input)
+}
+
fn read_name(input: &TokenStream) -> Ident {
if input.is_empty() {
Ident::new("ThisProgram", proc_macro2::Span::call_site())
diff --git a/mingling_macros/src/suggest.rs b/mingling_macros/src/suggest.rs
index 7354ff0..d3ab446 100644
--- a/mingling_macros/src/suggest.rs
+++ b/mingling_macros/src/suggest.rs
@@ -70,3 +70,24 @@ pub fn suggest(input: TokenStream) -> TokenStream {
expanded.into()
}
+
+pub fn suggest_enum(input: TokenStream) -> TokenStream {
+ let enum_type = parse_macro_input!(input as syn::Type);
+
+ let expanded = quote! {{
+ let mut enum_suggest = ::mingling::Suggest::new();
+ for (name, desc) in <#enum_type>::enums() {
+ if desc.is_empty() {
+ enum_suggest.insert(::mingling::SuggestItem::new(name.to_string()));
+ } else {
+ enum_suggest.insert(::mingling::SuggestItem::new_with_desc(
+ name.to_string(),
+ desc.to_string(),
+ ));
+ }
+ }
+ enum_suggest
+ }};
+
+ expanded.into()
+}