use std::collections::HashMap;
use heck::ToSnakeCase;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{Attribute, Data, DeriveInput, FieldsNamed, Generics, LitStr, Type};
use crate::util::{type_add_colon2, type_is_option};
pub fn find_attr_name(attrs: &Vec<Attribute>) -> Option<String> {
for attr in attrs {
let name = attr.meta.require_list().unwrap();
if name.path.is_ident("name") {
let name: LitStr = name.parse_args().unwrap();
return Some(name.value());
}
}
None
}
pub fn find_attr_converter(attrs: &Vec<Attribute>) -> Option<Type> {
for attr in attrs {
let ty = attr.meta.require_list().unwrap();
if ty.path.is_ident("converter") {
let ty: Type = ty.parse_args().unwrap();
return Some(ty);
}
}
None
}
pub struct FieldsNamedParser {
pub path_ident: Ident,
pub let_def_token: TokenStream,
pub assign_token: TokenStream,
pub if_field_token: TokenStream,
}
impl FieldsNamedParser {
pub fn new(named: FieldsNamed) -> Self {
let path_ident = Ident::new("__syn_path", Span::call_site());
let mut let_def_token = quote!();
let mut assign_token = quote!();
let mut if_field_token = quote!();
for named_item in named.named {
let converter_ty = find_attr_converter(&named_item.attrs);
let named_ident = named_item.ident;
let named_ident_str = named_ident.as_ref().map(|t| t.to_string());
let mut named_ty = named_item.ty;
let is_option = type_is_option(&named_ty);
type_add_colon2(&mut named_ty);
let parse_token = match converter_ty {
Some(c_ty) => {
quote!(#c_ty :: convert(&stream)?)
}
None => {
quote!(#named_ty :: try_from_expr(stream.parse()?)?)
}
};
if is_option {
let_def_token = quote! {
#let_def_token
let mut #named_ident: #named_ty = None;
};
if_field_token = quote! {
#if_field_token
if #path_ident .is_ident(#named_ident_str) {
#named_ident = #parse_token;
if stream.is_empty() {
break;
}
let _comma: syn::Token![,] = stream.parse()?;
continue;
}
};
assign_token = quote! {
#assign_token
#named_ident,
};
} else {
let_def_token = quote! {
#let_def_token
let mut #named_ident: Option<#named_ty> = None;
};
if_field_token = quote! {
#if_field_token
if #path_ident .is_ident(#named_ident_str) {
#named_ident = Some(#parse_token);
if stream.is_empty() {
break;
}
let _comma: syn::Token![,] = stream.parse()?;
continue;
}
};
let message = format!("missing`{}`", named_ident_str.unwrap_or_default());
assign_token = quote! {
#assign_token
#named_ident: #named_ident .ok_or_else(|| {
syn::Error::new(stream.span(), #message)
})?,
};
}
}
Self {
path_ident,
let_def_token,
assign_token,
if_field_token,
}
}
pub fn impl_syn_parse(&self, ty: &Ident) -> TokenStream {
let path_ident = &self.path_ident;
let let_def_token = &self.let_def_token;
let if_field_token = &self.if_field_token;
let assign_token = &self.assign_token;
quote! {
impl syn::parse::Parse for #ty {
fn parse(stream: ParseStream) -> Result<Self> {
#let_def_token
while !stream.is_empty() {
let #path_ident: Path = stream.parse()?;
let _eq_token: Token![=] = stream.parse()?;
#if_field_token
}
Ok(Self { #assign_token })
}
}
}
}
pub fn impl_meta_parse(&self, ty: &Ident, generics: &Generics) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
impl #impl_generics MetaParser for #ty #ty_generics #where_clause {
fn parse(meta: &Meta) -> syn::Result<Self> {
let list = meta.require_list()?;
list.parse_args_with(|stream: ParseStream| -> syn::Result<Self> {
<Self as syn::parse::Parse> :: parse(stream)
})
}
}
}
}
}
pub fn expand(input: DeriveInput) -> TokenStream {
let ty = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
match input.data {
Data::Struct(data) => match data.fields {
syn::Fields::Named(named) => {
let parser = FieldsNamedParser::new(named);
let impl_syn_parse = parser.impl_syn_parse(&ty);
let impl_meta_parse = parser.impl_meta_parse(&ty, &input.generics);
quote! {
#impl_syn_parse
#impl_meta_parse
}
},
syn::Fields::Unnamed(_) => unimplemented!(),
syn::Fields::Unit => unimplemented!(),
},
Data::Enum(data) => {
let mut result_token = quote!();
let mut group_map: HashMap<String, TokenStream> = HashMap::new();
for var_item in data.variants {
let var_ident = var_item.ident;
let attr_name = find_attr_name(&var_item.attrs)
.unwrap_or(var_ident.to_string().to_snake_case());
let return_token;
match var_item.fields {
syn::Fields::Named(named) => {
let fields_named_parser = FieldsNamedParser::new(named);
let let_def_token = fields_named_parser.let_def_token;
let if_field_token = fields_named_parser.if_field_token;
let assign_token = fields_named_parser.assign_token;
let path_ident = fields_named_parser.path_ident;
return_token = quote! {
let result = meta.require_list();
if let Ok(list) = result {
let result = list.parse_args_with(|stream: ParseStream| -> syn::Result<Self #ty_generics> {
#let_def_token
loop {
if stream.is_empty() {
break;
}
let #path_ident : Path = stream.parse()?;
let _eq_token: Token![=] = stream.parse()?;
#if_field_token
}
Ok(Self :: #var_ident {
#assign_token
})
});
if let Ok(result) = result {
return Ok(result);
}
}
};
}
syn::Fields::Unnamed(unamed) => {
let mut let_def_token = quote!();
let mut assign_token = quote!();
let mut if_field_token = quote!();
let mut index = 0;
for unamed_item in unamed.unnamed {
index += 1;
let mut unamed_ty = unamed_item.ty;
let converter_ty = find_attr_converter(&unamed_item.attrs);
type_add_colon2(&mut unamed_ty);
let unamed_ident =
Ident::new(format!("uname_{index}").as_str(), Span::call_site());
let_def_token = quote! {
#let_def_token
let mut #unamed_ident: Option<#unamed_ty> = None;
};
let parse_token = match converter_ty {
Some(ty) => {
quote!(#ty ::convert(&stream)?)
}
None => {
quote!(stream.parse()?)
}
};
if_field_token = quote! {
#if_field_token
if index == #index {
#unamed_ident = Some(#parse_token);
}
};
assign_token = quote! {
#assign_token
#unamed_ident .ok_or_else(|| {
syn::Error::new(stream.span(), "Expected list")
})?,
};
}
return_token = quote! {
let result = meta.require_list();
if let Ok(list) = result {
let result = list.parse_args_with(|stream: ParseStream| -> syn::Result<Self #ty_generics> {
#let_def_token
let mut index = 0;
loop {
if stream.is_empty() {
break;
}
index += 1;
#if_field_token
if stream.is_empty() {
break;
}
let _comma: syn::Token![,] = stream.parse()?;
}
Ok(Self :: #var_ident(#assign_token) )
});
if let Ok(result) = result {
return Ok(result);
}
}
};
}
syn::Fields::Unit => {
return_token = quote! {
let result = meta.require_path_only();
if result.is_ok() {
return Ok(Self :: #var_ident);
}
};
}
}
let taked_return_token = group_map.get(&attr_name);
match taked_return_token {
Some(taked_return_token) => {
group_map.insert(
attr_name,
quote! {
#taked_return_token
#return_token
},
);
}
None => {
group_map.insert(attr_name, return_token);
}
}
}
for (attr_name, return_token) in group_map {
result_token = quote! {
#result_token
if __syn_meta_path.is_ident(#attr_name) {
#return_token;
}
};
}
quote! {
impl #impl_generics MetaParser for #ty #ty_generics #where_clause {
fn parse(meta: &Meta) -> syn::Result<Self> {
let __syn_meta_path = meta.path();
#result_token
Err(Error::new(Span::call_site(), "unrecognized writing method"))
}
}
impl syn::parse::Parse for #ty #ty_generics #where_clause {
fn parse(stream: syn::parse::ParseStream) -> syn::Result<Self> {
let meta: Meta = stream.parse()?;
MetaParser::parse(&meta)
}
}
impl TryFrom<&Attribute> for #ty #ty_generics #where_clause {
type Error = syn::Error;
fn try_from(attr: &Attribute) -> syn::Result<Self> {
<Self as MetaParser>::parse(&(attr.meta))
}
}
}
}
Data::Union(_) => unimplemented!(),
}
}