token-parser-derive 0.5.0

Derive macros to make structs parsable into tokens
Documentation
#![deny(missing_docs)]

/*!
Derive macros to automatically implement the `Parsable` trait from `token-parser`.

Supports:

- Tuple structs: parsed positionally (`(value1 value2 …)`).
- Named structs: parsed with one sub-list per field in any order (`((name1 value1) (name2 value2) …)`). Requires the struct to implement `Default`. Unknown field names yield `UnknownField`. Last value wins on duplicates. Missing fields keep their default.
- Unit structs: parsed as the empty list `()`.
- `SymbolParsable`: parses a tuple struct's fields from the symbol string via `FromStr`.
*/

use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    Data, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics,
    Ident, TypeParamBound, parse_macro_input, parse_quote,
};

/// Derive the `Parsable` trait.
///
/// # Panics
///
/// Panics on union or enum types.
#[proc_macro_derive(Parsable)]
pub fn derive_default_parsable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    derive_parsable(input, false)
}

/// Derive the `Parsable` trait by parsing unnamed fields using the `FromStr` trait.
///
/// Parses the entire `name` string into each field.
///
/// # Panics
///
/// Panics if the input is not a tuple struct.
#[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, )* ))
        }
    }
}