Skip to main content

token_parser_derive/
lib.rs

1#![deny(missing_docs)]
2
3/*!
4Derive macros to automatically implement the `Parsable` trait from `token-parser`.
5
6Supports:
7
8- Tuple structs: parsed positionally (`(value1 value2 …)`).
9- 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.
10- Unit structs: parsed as the empty list `()`.
11- `SymbolParsable`: parses a tuple struct's fields from the symbol string via `FromStr`.
12*/
13
14use proc_macro2::TokenStream;
15use quote::quote;
16use syn::{
17    Data, DataStruct, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics,
18    Ident, TypeParamBound, parse_macro_input, parse_quote,
19};
20
21/// Derive the `Parsable` trait.
22///
23/// # Panics
24///
25/// Panics on union or enum types.
26#[proc_macro_derive(Parsable)]
27pub fn derive_default_parsable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
28    derive_parsable(input, false)
29}
30
31/// Derive the `Parsable` trait by parsing unnamed fields using the `FromStr` trait.
32///
33/// Parses the entire `name` string into each field.
34///
35/// # Panics
36///
37/// Panics if the input is not a tuple struct.
38#[proc_macro_derive(SymbolParsable)]
39pub fn derive_symbol_parsable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
40    derive_parsable(input, true)
41}
42
43fn add_trait_bounds(mut generics: Generics, bound: &TypeParamBound) -> Generics {
44    for param in &mut generics.params {
45        if let GenericParam::Type(type_param) = param {
46            type_param.bounds.push(bound.clone());
47        }
48    }
49    generics
50}
51
52#[expect(clippy::panic, reason = "proc-macro compile-time error signal")]
53fn derive_parsable(input: proc_macro::TokenStream, symbol: bool) -> proc_macro::TokenStream {
54    let input = parse_macro_input!(input as DeriveInput);
55    let name = input.ident;
56
57    let trait_bound = if symbol {
58        parse_quote!(::std::str::FromStr)
59    } else {
60        parse_quote!(::token_parser::Parsable<C>)
61    };
62
63    let (_, ty_generics, _) = input.generics.split_for_impl();
64
65    let mut generics = add_trait_bounds(input.generics.clone(), &trait_bound);
66    generics
67        .params
68        .push(parse_quote!(C: ::token_parser::Context));
69    let (impl_generics, _, where_clause) = generics.split_for_impl();
70
71    let body = match input.data {
72        Data::Struct(data) => struct_body(&name, data, symbol),
73        Data::Enum(_) => panic!("Deriving Parsable is not yet supported for enums"),
74        Data::Union(_) => panic!("Deriving Parsable is not supported for unions"),
75    };
76
77    let expanded = quote! {
78        impl #impl_generics ::token_parser::Parsable<C> for #name #ty_generics #where_clause {
79            #body
80        }
81    };
82
83    proc_macro::TokenStream::from(expanded)
84}
85
86#[expect(clippy::panic, reason = "proc-macro compile-time error signal")]
87fn struct_body(name: &Ident, data: DataStruct, symbol: bool) -> TokenStream {
88    if symbol {
89        let Fields::Unnamed(fields) = data.fields else {
90            panic!("SymbolParsable requires a tuple struct");
91        };
92        return symbol_tuple(name, &fields);
93    }
94    match data.fields {
95        Fields::Unnamed(fields) => parse_list_tuple(name, &fields),
96        Fields::Named(fields) => parse_list_named(name, &fields),
97        Fields::Unit => quote! {
98            fn parse_list(_parser: &mut ::token_parser::Parser, _context: &C) -> ::token_parser::Result<Self> {
99                Ok(Self)
100            }
101        },
102    }
103}
104
105fn parse_list_tuple(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
106    let exprs = fields.unnamed.iter().enumerate().map(|(position, _)| {
107        let description = format!("field {position} of `{name}`");
108        quote! {
109            parser
110                .parse_next(context)
111                .map_err(|error| error.context(#description))?
112        }
113    });
114    quote! {
115        fn parse_list(parser: &mut ::token_parser::Parser, context: &C) -> ::token_parser::Result<Self> {
116            Ok(Self( #( #exprs, )* ))
117        }
118    }
119}
120
121fn parse_list_named(name: &Ident, fields: &FieldsNamed) -> TokenStream {
122    let names: Vec<&Ident> = fields
123        .named
124        .iter()
125        .filter_map(|f| f.ident.as_ref())
126        .collect();
127    let labels: Vec<String> = names.iter().map(|n| Ident::to_string(n)).collect();
128
129    let arms = names.iter().zip(labels.iter()).map(|(field, label)| {
130        let description = format!("field `{label}` of `{name}`");
131        quote! {
132            #label => {
133                result.#field = sub
134                    .parse_next(context)
135                    .map_err(|error| error.context(#description))?;
136            }
137        }
138    });
139
140    let type_description = format!("`{name}`");
141    quote! {
142        fn parse_list(parser: &mut ::token_parser::Parser, context: &C) -> ::token_parser::Result<Self> {
143            let mut result = <Self as ::std::default::Default>::default();
144            for element in parser {
145                let mut sub = element?;
146                let field_name: Box<str> = sub.parse_next(context)?;
147                match field_name.as_ref() {
148                    #( #arms )*
149                    _ => {
150                        return Err(::token_parser::Error::from(
151                            ::token_parser::ErrorKind::UnknownField(field_name),
152                        )
153                        .at(sub.span())
154                        .context(#type_description));
155                    }
156                }
157            }
158            Ok(result)
159        }
160    }
161}
162
163fn symbol_tuple(name: &Ident, fields: &FieldsUnnamed) -> TokenStream {
164    let exprs = fields.unnamed.iter().map(|field| {
165        let field_type = &field.ty;
166        quote! {
167            name.parse().map_err(|error| ::token_parser::ErrorKind::StringParsing {
168                type_name: stringify!(#field_type),
169                source: Box::new(error),
170            })?
171        }
172    });
173    quote! {
174        fn parse_symbol(name: Box<str>, _span: ::token_parser::Span, _context: &C) -> ::token_parser::Result<Self> {
175            Ok(#name( #( #exprs, )* ))
176        }
177    }
178}