You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
4.9 KiB
Rust
170 lines
4.9 KiB
Rust
use proc_macro::TokenStream;
|
|
use std::collections::HashSet;
|
|
|
|
use proc_macro2::Span;
|
|
use quote::{quote, quote_spanned};
|
|
use syn::{Attribute, Data, DataEnum, DeriveInput, Fields, LitStr};
|
|
use syn::parse::{ParseBuffer, ParseStream};
|
|
use syn::spanned::Spanned;
|
|
|
|
use attribute::*;
|
|
use error::{MacroError, MacroResult};
|
|
|
|
mod error;
|
|
mod attribute;
|
|
|
|
const CRATE_ATTR: &'static str = "enumscribe";
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct Enum {
|
|
variants: Vec<Variant>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct Variant {
|
|
ident: String,
|
|
v_type: VariantType,
|
|
span: Span,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
enum VariantType {
|
|
Ignore,
|
|
Named { name: String, constructor: VariantConstructor },
|
|
Other { field_name: Option<String> }, //use {} for constructor if Some, use () if None
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
enum VariantConstructor {
|
|
None,
|
|
Paren,
|
|
Brace,
|
|
}
|
|
|
|
fn parse_enum(data: DataEnum) -> MacroResult<Enum> {
|
|
const NAME: &'static str = "str";
|
|
const OTHER: &'static str = "other";
|
|
const IGNORE: &'static str = "ignore";
|
|
|
|
let mut variants = Vec::with_capacity(data.variants.len());
|
|
let mut taken_names = HashSet::new();
|
|
|
|
for variant in data.variants {
|
|
let variant_ident = variant.ident.to_string();
|
|
let variant_span = variant.span();
|
|
|
|
let mut dict = Dict::from_attrs(CRATE_ATTR, &variant.attrs)?;
|
|
|
|
let name_opt = dict.remove_typed_value(NAME, Value::value_string)?;
|
|
|
|
let other = match dict.remove_typed_value(OTHER, Value::value_bool)? {
|
|
Some((other, _)) => other,
|
|
None => false
|
|
};
|
|
|
|
let ignore = match dict.remove_typed_value(IGNORE, Value::value_bool)? {
|
|
Some((ignore, _)) => ignore,
|
|
None => false
|
|
};
|
|
|
|
dict.assert_empty()?;
|
|
|
|
let scribe_variant = if ignore {
|
|
Variant {
|
|
ident: variant_ident,
|
|
v_type: VariantType::Ignore,
|
|
span: variant_span,
|
|
}
|
|
} else if other {
|
|
if let Some((_, name_span)) = name_opt {
|
|
return Err(MacroError::new(
|
|
format!(
|
|
"cannot use {} for variant {} because it is marked as {}",
|
|
NAME, variant_ident, OTHER
|
|
),
|
|
name_span,
|
|
));
|
|
}
|
|
|
|
if variant.fields.len() != 1 {
|
|
return Err(MacroError::new(
|
|
format!(
|
|
"the variant {} must have exactly one field because it is marked as {}",
|
|
variant_ident, OTHER
|
|
),
|
|
variant_span,
|
|
));
|
|
}
|
|
|
|
let field_name = variant.fields.iter().next()
|
|
.and_then(|field| field.ident.as_ref().map(|ident| ident.to_string()));
|
|
|
|
Variant {
|
|
ident: variant_ident,
|
|
v_type: VariantType::Other { field_name },
|
|
span: variant_span,
|
|
}
|
|
} else {
|
|
let (name, name_span) = match name_opt {
|
|
Some((name, name_span)) => (name, name_span),
|
|
None => (variant.ident.to_string(), variant.ident.span())
|
|
};
|
|
|
|
if taken_names.contains(&name) {
|
|
return Err(MacroError::new(
|
|
format!("duplicate name \"{}\"", name),
|
|
name_span,
|
|
));
|
|
}
|
|
|
|
if variant.fields.len() != 0 {
|
|
return Err(MacroError::new(
|
|
format!(
|
|
"the variant {} must not have any fields",
|
|
variant_ident
|
|
),
|
|
variant_span,
|
|
));
|
|
}
|
|
|
|
let constructor = match variant.fields {
|
|
Fields::Named(_) => VariantConstructor::Brace,
|
|
Fields::Unnamed(_) => VariantConstructor::Paren,
|
|
Fields::Unit => VariantConstructor::None,
|
|
};
|
|
|
|
taken_names.insert(name.clone());
|
|
|
|
Variant {
|
|
ident: variant_ident,
|
|
v_type: VariantType::Named { name, constructor },
|
|
span: variant_span,
|
|
}
|
|
};
|
|
|
|
variants.push(scribe_variant);
|
|
}
|
|
|
|
Ok(Enum { variants })
|
|
}
|
|
|
|
#[proc_macro_derive(EnumToString, attributes(enumscribe))]
|
|
pub fn derive_enum_to_string(input: TokenStream) -> TokenStream {
|
|
let input: DeriveInput = syn::parse(input).unwrap();
|
|
|
|
let enum_data = match input.data {
|
|
Data::Enum(data) => data,
|
|
Data::Struct(_) => return MacroError::new("cannot use enumscribe for structs", input.ident.span()).to_token_stream(),
|
|
Data::Union(_) => return MacroError::new("cannot use enumscribe for unions", input.ident.span()).to_token_stream()
|
|
};
|
|
|
|
let variants = match parse_enum(enum_data) {
|
|
Ok(variants) => variants,
|
|
Err(err) => return err.to_token_stream()
|
|
};
|
|
|
|
println!("{:?}", variants);
|
|
|
|
TokenStream::new()
|
|
}
|