use crate::attribute;
use proc_macro::TokenStream;
use quote::quote;
struct ParsingMacro {
recursion_attr: &'static str,
}
impl ParsingMacro {
fn impl_fields(
&self,
fields: syn::punctuated::Iter<'_, syn::Field>,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let mut gen = proc_macro2::TokenStream::new();
for syn::Field {
ty, ident, attrs, ..
} in fields
{
if let Some(name) = ident {
gen.extend(quote! { #name: });
}
if let Some(&_) = attrs.iter().find(attribute::is(self.recursion_attr)) {
gen.extend(quote! {
{
let clipv::parser::Parsed ( value, rest ) = #ty::try_parse(values)?;
values = rest;
value
},
});
} else {
gen.extend(quote! { values.next().map_or(Err(clipv::parser::ParsingError::TooFewArguments), |value| value.to_string().parse::<#ty>().or(Err(clipv::parser::ParsingError::BadType)))?, });
}
}
Ok(gen)
}
fn impl_object_initialisation(
&self,
ident: &syn::Ident,
fields: &syn::Fields,
) -> Result<proc_macro2::TokenStream, syn::Error> {
match fields {
syn::Fields::Unit => Ok(quote! { #ident }),
syn::Fields::Named(syn::FieldsNamed { named, .. }) => {
let fields = self.impl_fields(named.iter())?;
Ok(quote! { #ident { #fields } })
}
syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) => {
let fields = self.impl_fields(unnamed.iter())?;
Ok(quote! { #ident ( #fields ) })
}
}
}
fn impl_enum_initialization(
&self,
parent: &syn::Ident,
variants: syn::punctuated::Iter<'_, syn::Variant>,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let mut gen = proc_macro2::TokenStream::new();
for syn::Variant { ident, fields, .. } in variants {
let lowercase = ident.to_string().to_lowercase();
let value = self.impl_object_initialisation(ident, fields)?;
gen.extend(quote! {
#lowercase => Ok(#parent::#value),
});
}
Ok(quote! {
{
let keyword = values.next().ok_or(clipv::parser::ParsingError::TooFewArguments)?;
match keyword.to_string().to_lowercase().as_str() {
#gen
_ => Err(clipv::parser::ParsingError::VariantNotFound)
}
}?
})
}
fn impl_parser(&self, ident: &syn::Ident, data: &syn::Data) -> proc_macro2::TokenStream {
match data {
syn::Data::Struct(syn::DataStruct { fields, .. }) => {
self.impl_object_initialisation(ident, fields)
}
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
self.impl_enum_initialization(ident, variants.iter())
}
syn::Data::Union(syn::DataUnion { union_token, .. }) => Err(syn::Error::new_spanned(
union_token,
"Unsupported Union type",
)),
}
.unwrap_or_else(|err| err.to_compile_error())
}
}
pub(crate) fn impl_try_parse_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let parser = ParsingMacro {
recursion_attr: "try_parse",
}
.impl_parser(name, &ast.data);
quote! {
impl<'a, T> clipv::parser::TryParse<T> for #name where T: std::string::ToString +std::fmt::Display {
type Error = clipv::parser::ParsingError;
fn try_parse<I: std::iter::Iterator<Item = T>>(mut values: I) -> Result<clipv::parser::Parsed<Self, I>, Self::Error> {
Ok(clipv::parser::Parsed((#parser), values))
}
}
}
.into()
}