#![deny(missing_docs)]
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
Data, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics,
Ident, TypeParamBound, parse_macro_input, parse_quote,
};
#[proc_macro_derive(Parsable)]
pub fn derive_default_parsable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_parsable(input, false)
}
#[proc_macro_derive(SymbolParsable)]
pub fn derive_symbol_parsable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_parsable(input, true)
}
fn add_trait_bounds(mut generics: Generics, bound: TypeParamBound) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(type_param) = param {
type_param.bounds.push(bound.clone());
}
}
generics
}
fn derive_parsable(input: proc_macro::TokenStream, symbol: bool) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let trait_bound = if symbol {
parse_quote!(::std::str::FromStr)
} else {
parse_quote!(::token_parser::Parsable<C>)
};
let (_, ty_generics, _) = input.generics.split_for_impl();
let mut generics = add_trait_bounds(input.generics.clone(), trait_bound);
generics
.params
.push(parse_quote!(C: ::token_parser::Context));
let (impl_generics, _, where_clause) = generics.split_for_impl();
let body = match input.data {
Data::Struct(data) => struct_body(&name, data, symbol),
Data::Enum(_) => panic!("Deriving Parsable is not yet supported for enums"),
Data::Union(_) => panic!("Deriving Parsable is not supported for unions"),
};
let expanded = quote! {
impl #impl_generics ::token_parser::Parsable<C> for #name #ty_generics #where_clause {
#body
}
};
proc_macro::TokenStream::from(expanded)
}
fn struct_body(name: &Ident, data: DataStruct, symbol: bool) -> TokenStream {
if symbol {
let Fields::Unnamed(fields) = data.fields else {
panic!("SymbolParsable requires a tuple struct");
};
return symbol_tuple(name, &fields);
}
match data.fields {
Fields::Unnamed(fields) => parse_list_tuple(name, &fields),
Fields::Named(fields) => parse_list_named(name, &fields),
Fields::Unit => quote! {
fn parse_list(_parser: &mut ::token_parser::Parser, _context: &C) -> ::token_parser::Result<Self> {
Ok(Self)
}
},
}
}
fn parse_list_tuple(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
let exprs = fields.unnamed.iter().enumerate().map(|(position, _)| {
let description = format!("field {position} of `{name}`");
quote! {
parser
.parse_next(context)
.map_err(|error| error.context(#description))?
}
});
quote! {
fn parse_list(parser: &mut ::token_parser::Parser, context: &C) -> ::token_parser::Result<Self> {
Ok(Self( #( #exprs, )* ))
}
}
}
fn parse_list_named(name: &Ident, fields: &FieldsNamed) -> TokenStream {
let names: Vec<&Ident> = fields
.named
.iter()
.map(|f| f.ident.as_ref().unwrap())
.collect();
let labels: Vec<String> = names.iter().map(|n| Ident::to_string(n)).collect();
let arms = names.iter().zip(labels.iter()).map(|(field, label)| {
let description = format!("field `{label}` of `{name}`");
quote! {
#label => {
result.#field = sub
.parse_next(context)
.map_err(|error| error.context(#description))?;
}
}
});
let type_description = format!("`{name}`");
quote! {
fn parse_list(parser: &mut ::token_parser::Parser, context: &C) -> ::token_parser::Result<Self> {
let mut result = <Self as ::std::default::Default>::default();
for element in parser {
let mut sub = element?;
let field_name: Box<str> = sub.parse_next(context)?;
match field_name.as_ref() {
#( #arms )*
_ => {
return Err(::token_parser::Error::from(
::token_parser::ErrorKind::UnknownField(field_name),
)
.at(sub.span())
.context(#type_description));
}
}
}
Ok(result)
}
}
}
fn symbol_tuple(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
let exprs = fields.unnamed.iter().map(|field| {
let field_type = &field.ty;
quote! {
name.parse().map_err(|_| ::token_parser::ErrorKind::StringParsing(stringify!(#field_type)))?
}
});
quote! {
fn parse_symbol(name: Box<str>, _span: ::token_parser::Span, _context: &C) -> ::token_parser::Result<Self> {
Ok(#name( #( #exprs, )* ))
}
}
}