procmeta-core 0.3.5

proc-macro helper
Documentation
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!(),
    }
}